mirror of
https://github.com/servo/servo.git
synced 2025-07-03 21:43:41 +01:00
Update web-platform-tests to revision e92532746b7615dcccdfa060937a87664816b1db
This commit is contained in:
parent
cccca27f4f
commit
726b56aa12
149 changed files with 22796 additions and 1884 deletions
|
@ -122129,18 +122129,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-inflow-position.html": [
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-inflow-position.html",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-inflow-position-ref.html",
|
|
||||||
"=="
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-inline.html": [
|
"css/css-position/position-sticky-inline.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-inline.html",
|
"/css/css-position/position-sticky-inline.html",
|
||||||
|
@ -122153,18 +122141,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-margins.html": [
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-margins.html",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-margins-ref.html",
|
|
||||||
"=="
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-nested-bottom.html": [
|
"css/css-position/position-sticky-nested-bottom.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-nested-bottom.html",
|
"/css/css-position/position-sticky-nested-bottom.html",
|
||||||
|
@ -122237,18 +122213,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-overflow-padding.html": [
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-overflow-padding.html",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-overflow-padding-ref.html",
|
|
||||||
"=="
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-rendering.html": [
|
"css/css-position/position-sticky-rendering.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-rendering.html",
|
"/css/css-position/position-sticky-rendering.html",
|
||||||
|
@ -122261,18 +122225,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-root-scroller.html": [
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-root-scroller.html",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-root-scroller-ref.html",
|
|
||||||
"=="
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-stacking-context.html": [
|
"css/css-position/position-sticky-stacking-context.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-stacking-context.html",
|
"/css/css-position/position-sticky-stacking-context.html",
|
||||||
|
@ -122393,30 +122345,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-transforms-translate.html": [
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-transforms-translate.html",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-transforms-translate-ref.html",
|
|
||||||
"=="
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-transforms.html": [
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-transforms.html",
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"/css/css-position/position-sticky-transforms-ref.html",
|
|
||||||
"=="
|
|
||||||
]
|
|
||||||
],
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-writing-modes.html": [
|
"css/css-position/position-sticky-writing-modes.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-writing-modes.html",
|
"/css/css-position/position-sticky-writing-modes.html",
|
||||||
|
@ -209180,6 +209108,21 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"conformance-checkers/html/elements/script/streams-demo-append-child-isvalid.html": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"conformance-checkers/html/elements/script/streams-demo-streaming-element-backpressure-isvalid.html": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"conformance-checkers/html/elements/script/streams-demo-streaming-element-isvalid.html": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"conformance-checkers/html/elements/small/model-isvalid.html": [
|
"conformance-checkers/html/elements/small/model-isvalid.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -249005,21 +248948,11 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-inflow-position-ref.html": [
|
|
||||||
[
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-inline-ref.html": [
|
"css/css-position/position-sticky-inline-ref.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-margins-ref.html": [
|
|
||||||
[
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-nested-bottom-ref.html": [
|
"css/css-position/position-sticky-nested-bottom-ref.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -249050,21 +248983,11 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-overflow-padding-ref.html": [
|
|
||||||
[
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-rendering-ref.html": [
|
"css/css-position/position-sticky-rendering-ref.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-root-scroller-ref.html": [
|
|
||||||
[
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-stacking-context-ref.html": [
|
"css/css-position/position-sticky-stacking-context-ref.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -249115,16 +249038,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-transforms-ref.html": [
|
|
||||||
[
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-transforms-translate-ref.html": [
|
|
||||||
[
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-writing-modes-ref.html": [
|
"css/css-position/position-sticky-writing-modes-ref.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -271730,6 +271643,11 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"fetch/data-urls/README.md": [
|
"fetch/data-urls/README.md": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -291115,6 +291033,11 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html": [
|
"service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -291685,6 +291608,11 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"service-workers/service-worker/resources/object-is-not-intercepted-iframe.html": [
|
"service-workers/service-worker/resources/object-is-not-intercepted-iframe.html": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -312579,6 +312507,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"css/css-position/position-sticky-inflow-position.html": [
|
||||||
|
[
|
||||||
|
"/css/css-position/position-sticky-inflow-position.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"css/css-position/position-sticky-input-box-gets-focused-after-scroll.html": [
|
"css/css-position/position-sticky-input-box-gets-focused-after-scroll.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-input-box-gets-focused-after-scroll.html",
|
"/css/css-position/position-sticky-input-box-gets-focused-after-scroll.html",
|
||||||
|
@ -312591,6 +312525,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"css/css-position/position-sticky-margins.html": [
|
||||||
|
[
|
||||||
|
"/css/css-position/position-sticky-margins.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"css/css-position/position-sticky-offset-overflow.html": [
|
"css/css-position/position-sticky-offset-overflow.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-offset-overflow.html",
|
"/css/css-position/position-sticky-offset-overflow.html",
|
||||||
|
@ -312603,6 +312543,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"css/css-position/position-sticky-overflow-padding.html": [
|
||||||
|
[
|
||||||
|
"/css/css-position/position-sticky-overflow-padding.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"css/css-position/position-sticky-parsing.html": [
|
"css/css-position/position-sticky-parsing.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-parsing.html",
|
"/css/css-position/position-sticky-parsing.html",
|
||||||
|
@ -312615,12 +312561,30 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"css/css-position/position-sticky-root-scroller.html": [
|
||||||
|
[
|
||||||
|
"/css/css-position/position-sticky-root-scroller.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"css/css-position/position-sticky-top.html": [
|
"css/css-position/position-sticky-top.html": [
|
||||||
[
|
[
|
||||||
"/css/css-position/position-sticky-top.html",
|
"/css/css-position/position-sticky-top.html",
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"css/css-position/position-sticky-transforms-translate.html": [
|
||||||
|
[
|
||||||
|
"/css/css-position/position-sticky-transforms-translate.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"css/css-position/position-sticky-transforms.html": [
|
||||||
|
[
|
||||||
|
"/css/css-position/position-sticky-transforms.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"css/css-pseudo/first-letter-property-whitelist.html": [
|
"css/css-pseudo/first-letter-property-whitelist.html": [
|
||||||
[
|
[
|
||||||
"/css/css-pseudo/first-letter-property-whitelist.html",
|
"/css/css-pseudo/first-letter-property-whitelist.html",
|
||||||
|
@ -315405,12 +315369,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/computed/getProperties.tentative.html": [
|
|
||||||
[
|
|
||||||
"/css/css-typed-om/the-stylepropertymap/computed/getProperties.tentative.html",
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-typed-om/the-stylepropertymap/computed/has.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/computed/has.tentative.html": [
|
||||||
[
|
[
|
||||||
"/css/css-typed-om/the-stylepropertymap/computed/has.tentative.html",
|
"/css/css-typed-om/the-stylepropertymap/computed/has.tentative.html",
|
||||||
|
@ -315471,12 +315429,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/declared/getProperties.tentative.html": [
|
|
||||||
[
|
|
||||||
"/css/css-typed-om/the-stylepropertymap/declared/getProperties.tentative.html",
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-typed-om/the-stylepropertymap/declared/has.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/declared/has.tentative.html": [
|
||||||
[
|
[
|
||||||
"/css/css-typed-om/the-stylepropertymap/declared/has.tentative.html",
|
"/css/css-typed-om/the-stylepropertymap/declared/has.tentative.html",
|
||||||
|
@ -315537,12 +315489,6 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/inline/getProperties.tentative.html": [
|
|
||||||
[
|
|
||||||
"/css/css-typed-om/the-stylepropertymap/inline/getProperties.tentative.html",
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"css/css-typed-om/the-stylepropertymap/inline/has.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/inline/has.tentative.html": [
|
||||||
[
|
[
|
||||||
"/css/css-typed-om/the-stylepropertymap/inline/has.tentative.html",
|
"/css/css-typed-om/the-stylepropertymap/inline/has.tentative.html",
|
||||||
|
@ -323459,6 +323405,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"fetch/corb/script-html-via-cross-origin-blob-url.sub.html": [
|
||||||
|
[
|
||||||
|
"/fetch/corb/script-html-via-cross-origin-blob-url.sub.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html": [
|
"fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html": [
|
||||||
[
|
[
|
||||||
"/fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html",
|
"/fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html",
|
||||||
|
@ -327749,6 +327701,12 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html": [
|
||||||
|
[
|
||||||
|
"/html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html",
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"html/semantics/embedded-content/media-elements/error-codes/error.html": [
|
"html/semantics/embedded-content/media-elements/error-codes/error.html": [
|
||||||
[
|
[
|
||||||
"/html/semantics/embedded-content/media-elements/error-codes/error.html",
|
"/html/semantics/embedded-content/media-elements/error-codes/error.html",
|
||||||
|
@ -414540,6 +414498,18 @@
|
||||||
"e2b95f87aee2b09233d324c898e02629ff83f8ce",
|
"e2b95f87aee2b09233d324c898e02629ff83f8ce",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
|
"conformance-checkers/html/elements/script/streams-demo-append-child-isvalid.html": [
|
||||||
|
"8e4bdab31d350439f73114ac52cbed0f0eb4b83d",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
|
"conformance-checkers/html/elements/script/streams-demo-streaming-element-backpressure-isvalid.html": [
|
||||||
|
"cb0a490ebe790e4c7fc10ab5908613d4bc555c3f",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
|
"conformance-checkers/html/elements/script/streams-demo-streaming-element-isvalid.html": [
|
||||||
|
"7d7362011ddab3afc7af925aeebd067188e974e1",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
"conformance-checkers/html/elements/small/model-isvalid.html": [
|
"conformance-checkers/html/elements/small/model-isvalid.html": [
|
||||||
"ed1c313412f5af0134a36fe72fa75b566c83d555",
|
"ed1c313412f5af0134a36fe72fa75b566c83d555",
|
||||||
"support"
|
"support"
|
||||||
|
@ -501641,7 +501611,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/registered-properties-in-custom-paint.https.html": [
|
"css/css-paint-api/registered-properties-in-custom-paint.https.html": [
|
||||||
"de1e6dabe19bdd355060a62c7d697c64f49929fe",
|
"dc51ea1e448fc91b632363d5ea394f5a4a90bd06",
|
||||||
"reftest"
|
"reftest"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/resources/html5.png": [
|
"css/css-paint-api/resources/html5.png": [
|
||||||
|
@ -501653,7 +501623,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/style-background-image.https.html": [
|
"css/css-paint-api/style-background-image.https.html": [
|
||||||
"c3359a8ee23b9c3d9007066235aef894ddbaf47f",
|
"18660b13079289c93431e7ca6111d1c6c7adca80",
|
||||||
"reftest"
|
"reftest"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/style-before-pseudo-ref.html": [
|
"css/css-paint-api/style-before-pseudo-ref.html": [
|
||||||
|
@ -501661,7 +501631,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/style-before-pseudo.https.html": [
|
"css/css-paint-api/style-before-pseudo.https.html": [
|
||||||
"cd16dfc6e532851b23abdf47cb4cc89f20a6c655",
|
"a46f84e13e91ea23129a557893fcb929d6e191ed",
|
||||||
"reftest"
|
"reftest"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/style-first-letter-pseudo-ref.html": [
|
"css/css-paint-api/style-first-letter-pseudo-ref.html": [
|
||||||
|
@ -501669,7 +501639,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/style-first-letter-pseudo.https.html": [
|
"css/css-paint-api/style-first-letter-pseudo.https.html": [
|
||||||
"996d9f2e93bc8ba0a54415d0ffcab4cdb7c1ffa7",
|
"180ec110178f0cf9635b19309abb416e79ebf135",
|
||||||
"reftest"
|
"reftest"
|
||||||
],
|
],
|
||||||
"css/css-paint-api/valid-image-after-load-ref.html": [
|
"css/css-paint-api/valid-image-after-load-ref.html": [
|
||||||
|
@ -501796,13 +501766,9 @@
|
||||||
"a06a40f39b4a748c111dc01281261c5451204f95",
|
"a06a40f39b4a748c111dc01281261c5451204f95",
|
||||||
"reftest"
|
"reftest"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-inflow-position-ref.html": [
|
|
||||||
"bcce2ded8073a7b5b3477bcf90157cb0e77c2b40",
|
|
||||||
"support"
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-inflow-position.html": [
|
"css/css-position/position-sticky-inflow-position.html": [
|
||||||
"c8e2bcdddf9e8ee93f9306d88b96c3bf1f1bfaf6",
|
"a0fec7d91b7261987e1f2fa5efca966d6f37bc1e",
|
||||||
"reftest"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-inline-ref.html": [
|
"css/css-position/position-sticky-inline-ref.html": [
|
||||||
"9458cab53d2065e4893d127ee0097bbd53c6b898",
|
"9458cab53d2065e4893d127ee0097bbd53c6b898",
|
||||||
|
@ -501820,13 +501786,9 @@
|
||||||
"2a04672cdac818a6887eac7d6824ea85d3d0559d",
|
"2a04672cdac818a6887eac7d6824ea85d3d0559d",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-margins-ref.html": [
|
|
||||||
"0cdb788c913f47a121114ac5b8e6a140bb08c1ff",
|
|
||||||
"support"
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-margins.html": [
|
"css/css-position/position-sticky-margins.html": [
|
||||||
"72fb6ae7d97bf2448ebd68ccf110edd6bae2c92f",
|
"3f6bc9537adf2a4d477f99866d73f42b65c26db3",
|
||||||
"reftest"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-nested-bottom-ref.html": [
|
"css/css-position/position-sticky-nested-bottom-ref.html": [
|
||||||
"59a8e46358a8a5bf8638a2d1982c63becef5bc77",
|
"59a8e46358a8a5bf8638a2d1982c63becef5bc77",
|
||||||
|
@ -501884,13 +501846,9 @@
|
||||||
"a25b64d016644c272ea92b6129a59eefb21d2fa0",
|
"a25b64d016644c272ea92b6129a59eefb21d2fa0",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-overflow-padding-ref.html": [
|
|
||||||
"b3d81934cc90e70dff6bc5cd7789594a8fcd7ecf",
|
|
||||||
"support"
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-overflow-padding.html": [
|
"css/css-position/position-sticky-overflow-padding.html": [
|
||||||
"588502dc7eb4a7f88f78dd1b2cdc857861c89f77",
|
"4a1e1c29bb47027e74437acdc1cfe073ea774f0f",
|
||||||
"reftest"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-parsing.html": [
|
"css/css-position/position-sticky-parsing.html": [
|
||||||
"224bc984bc6eb4a55931461cf7e51f7b04d219f4",
|
"224bc984bc6eb4a55931461cf7e51f7b04d219f4",
|
||||||
|
@ -501908,13 +501866,9 @@
|
||||||
"80caf6fb1e6c84dbf3e371a11166ac5b71bba687",
|
"80caf6fb1e6c84dbf3e371a11166ac5b71bba687",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-root-scroller-ref.html": [
|
|
||||||
"b66947a9f1b39c6c489267477d0122eeaeac7341",
|
|
||||||
"support"
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-root-scroller.html": [
|
"css/css-position/position-sticky-root-scroller.html": [
|
||||||
"8f77892b5a205a392942649476be7d5d54a91788",
|
"b7adc454ecf8b4950c46d99db11cf669b1a5d695",
|
||||||
"reftest"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-stacking-context-ref.html": [
|
"css/css-position/position-sticky-stacking-context-ref.html": [
|
||||||
"dd6e5d4734c924c1ad08d14db986fb89d7cb03f6",
|
"dd6e5d4734c924c1ad08d14db986fb89d7cb03f6",
|
||||||
|
@ -502000,21 +501954,13 @@
|
||||||
"bfd49209889fc14cae5af8d7c5e7990fbde451ec",
|
"bfd49209889fc14cae5af8d7c5e7990fbde451ec",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-transforms-ref.html": [
|
|
||||||
"b01ae263ac6b712912ba2af06edbaeaf75ba0215",
|
|
||||||
"support"
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-transforms-translate-ref.html": [
|
|
||||||
"49d0db4c6b27c9f66bd58f5a075d024cbeaeb076",
|
|
||||||
"support"
|
|
||||||
],
|
|
||||||
"css/css-position/position-sticky-transforms-translate.html": [
|
"css/css-position/position-sticky-transforms-translate.html": [
|
||||||
"71bdb184c1ad2d1405f683e05a5b4117c8c7362a",
|
"3fe5eb51028f4036f287601e3861dbd1377c588f",
|
||||||
"reftest"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-transforms.html": [
|
"css/css-position/position-sticky-transforms.html": [
|
||||||
"c3d2c2b167bcf6b8e7c45b90d9a797a216c27632",
|
"1b273a96f9f47c4bbbee71f41a2ceb046f7e9425",
|
||||||
"reftest"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-position/position-sticky-writing-modes-ref.html": [
|
"css/css-position/position-sticky-writing-modes-ref.html": [
|
||||||
"407a1831479ccca61f6f7b268abcbf97f667f0bf",
|
"407a1831479ccca61f6f7b268abcbf97f667f0bf",
|
||||||
|
@ -519041,7 +518987,7 @@
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html": [
|
||||||
"460bdf5cc3e35360eb8d910274cef440837f0eaf",
|
"23845856ad2da4422dc081dd8f4cc1a5d6aecfe3",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/computed/get-invalid.html": [
|
"css/css-typed-om/the-stylepropertymap/computed/get-invalid.html": [
|
||||||
|
@ -519056,10 +519002,6 @@
|
||||||
"2c9c39367ccf02f014b8bf62057f810a70ff58bf",
|
"2c9c39367ccf02f014b8bf62057f810a70ff58bf",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/computed/getProperties.tentative.html": [
|
|
||||||
"9e0018aed64fbca86d25f5ec466f88212ebd4906",
|
|
||||||
"testharness"
|
|
||||||
],
|
|
||||||
"css/css-typed-om/the-stylepropertymap/computed/has.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/computed/has.tentative.html": [
|
||||||
"b7b16ab44745e235883303b8c495aa1ceb874d0c",
|
"b7b16ab44745e235883303b8c495aa1ceb874d0c",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
@ -519077,7 +519019,7 @@
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html": [
|
||||||
"7a6bbc1b04f6d1ee82332ddbf58a27604645a86b",
|
"9ea176173ce999d17f26a80fc05b608532e471e0",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/declared/delete-invalid.html": [
|
"css/css-typed-om/the-stylepropertymap/declared/delete-invalid.html": [
|
||||||
|
@ -519100,10 +519042,6 @@
|
||||||
"df90a13e37ebcdbcf046e66f7e90ad2fc7edd9df",
|
"df90a13e37ebcdbcf046e66f7e90ad2fc7edd9df",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/declared/getProperties.tentative.html": [
|
|
||||||
"7cb65bf76de24fe33a48178f5193bc05c5ec3f44",
|
|
||||||
"testharness"
|
|
||||||
],
|
|
||||||
"css/css-typed-om/the-stylepropertymap/declared/has.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/declared/has.tentative.html": [
|
||||||
"3299b5537f2d535988f2f6dac65b3eaba63833b2",
|
"3299b5537f2d535988f2f6dac65b3eaba63833b2",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
@ -519144,10 +519082,6 @@
|
||||||
"2a8c50a83cbfd1c3da5614daac7070df0f5c83ff",
|
"2a8c50a83cbfd1c3da5614daac7070df0f5c83ff",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/inline/getProperties.tentative.html": [
|
|
||||||
"5187e1eb4fb23b6b9a7fba8130d2649c4ea9152e",
|
|
||||||
"testharness"
|
|
||||||
],
|
|
||||||
"css/css-typed-om/the-stylepropertymap/inline/has.tentative.html": [
|
"css/css-typed-om/the-stylepropertymap/inline/has.tentative.html": [
|
||||||
"89b6d05db8717f1bc1f82690706d849a57104ec7",
|
"89b6d05db8717f1bc1f82690706d849a57104ec7",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
@ -519209,7 +519143,7 @@
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js": [
|
"css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js": [
|
||||||
"3ac46f9655cf07f63761850d38eb0c0cebdd98bf",
|
"8f23e63849f184553cdc43d2c45aec316300db0a",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"css/css-typed-om/the-stylepropertymap/properties/right.html": [
|
"css/css-typed-om/the-stylepropertymap/properties/right.html": [
|
||||||
|
@ -543641,7 +543575,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"domparsing/XMLSerializer-serializeToString.html": [
|
"domparsing/XMLSerializer-serializeToString.html": [
|
||||||
"71314752b8552c19b0951647594b9c5c85ca01b6",
|
"0ff2295477d3d2d690b19976eceae8a8a79720fe",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"domparsing/createContextualFragment.html": [
|
"domparsing/createContextualFragment.html": [
|
||||||
|
@ -546673,7 +546607,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"feature-policy/picture-in-picture-default-feature-policy.https.sub.html": [
|
"feature-policy/picture-in-picture-default-feature-policy.https.sub.html": [
|
||||||
"3d493f9c8c024c4f97a5f0ae3d12977f8719bcc6",
|
"49c95204e8d3478f6c4509740fa0ed605146ca36",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html": [
|
"feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html": [
|
||||||
|
@ -547629,7 +547563,7 @@
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"fetch/corb/README.md": [
|
"fetch/corb/README.md": [
|
||||||
"5dd841770382cd2f6f1a09dca1103ef146bc912a",
|
"95e1cbd716945e9cc53297243af4cec1ac35493e",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"fetch/corb/img-html-correctly-labeled.sub-expected.html": [
|
"fetch/corb/img-html-correctly-labeled.sub-expected.html": [
|
||||||
|
@ -547732,10 +547666,18 @@
|
||||||
"41e260e7df49e0e4ddb1fc5df11913dbda15edd7",
|
"41e260e7df49e0e4ddb1fc5df11913dbda15edd7",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
|
"fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html": [
|
||||||
|
"b9cd3923eeb88157f3bc3f2e62b5ee5e3f166c0c",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
"fetch/corb/script-html-correctly-labeled.tentative.sub.html": [
|
"fetch/corb/script-html-correctly-labeled.tentative.sub.html": [
|
||||||
"71cb97517821177aa1c1d116f830cd2315963d18",
|
"71cb97517821177aa1c1d116f830cd2315963d18",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
|
"fetch/corb/script-html-via-cross-origin-blob-url.sub.html": [
|
||||||
|
"5fd4e36f6ad89ea7e7ede4c1a54136d80b126dd4",
|
||||||
|
"testharness"
|
||||||
|
],
|
||||||
"fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html": [
|
"fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html": [
|
||||||
"ff421a4a827db6c8eeec97d2a7ee7010fd8fd686",
|
"ff421a4a827db6c8eeec97d2a7ee7010fd8fd686",
|
||||||
"testharness"
|
"testharness"
|
||||||
|
@ -559400,6 +559342,10 @@
|
||||||
"33bf062c202724a1ca7cd6db052614317acba1f8",
|
"33bf062c202724a1ca7cd6db052614317acba1f8",
|
||||||
"manual"
|
"manual"
|
||||||
],
|
],
|
||||||
|
"html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html": [
|
||||||
|
"98f06519aeb619c9cf1db9ab3d94e987dffa182a",
|
||||||
|
"testharness"
|
||||||
|
],
|
||||||
"html/semantics/embedded-content/media-elements/contains.json": [
|
"html/semantics/embedded-content/media-elements/contains.json": [
|
||||||
"c4e395b396f7f8d9aac3e687d34c2df47dde5589",
|
"c4e395b396f7f8d9aac3e687d34c2df47dde5589",
|
||||||
"support"
|
"support"
|
||||||
|
@ -588277,7 +588223,7 @@
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/embed-and-object-are-not-intercepted.https.html": [
|
"service-workers/service-worker/embed-and-object-are-not-intercepted.https.html": [
|
||||||
"a9f08518e369c81036ce0815aab3f1a6895fb6ab",
|
"04b303491f89d1ec808548008aaa0b0747ab8672",
|
||||||
"testharness"
|
"testharness"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/extendable-event-async-waituntil.https.html": [
|
"service-workers/service-worker/extendable-event-async-waituntil.https.html": [
|
||||||
|
@ -588977,7 +588923,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/resources/echo-content.py": [
|
"service-workers/service-worker/resources/echo-content.py": [
|
||||||
"d22a9fe2b426436a8e4901d82ca68833eeb905b3",
|
"5a681fb53d70cf71d8d9ba13348025bee019ee1d",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/resources/echo-message-to-source-worker.js": [
|
"service-workers/service-worker/resources/echo-message-to-source-worker.js": [
|
||||||
|
@ -588985,11 +588931,15 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js": [
|
"service-workers/service-worker/resources/embed-and-object-are-not-intercepted-worker.js": [
|
||||||
"30d39c9d08dff37858a1ba843d7b51aaa559238b",
|
"ce5630c7ed59cfd0c0bd2e8e0e1bd596260e4e39",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
|
"service-workers/service-worker/resources/embed-image-is-not-intercepted-iframe.html": [
|
||||||
|
"686ef2a4531b7631bee58a2450c62a7532c6c8aa",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html": [
|
"service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html": [
|
||||||
"e33a11c8d4a2a14693f8eb5eb5feb8218c92de6f",
|
"e810456ed08cfa3be1730291b28b0f8134c093ed",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"service-workers/service-worker/resources/embedded-content-from-server.html": [
|
"service-workers/service-worker/resources/embedded-content-from-server.html": [
|
||||||
|
@ -589444,6 +589394,10 @@
|
||||||
"ec72a4c120ccfac3a165576f59a0e02b945343b3",
|
"ec72a4c120ccfac3a165576f59a0e02b945343b3",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
|
"service-workers/service-worker/resources/object-image-is-not-intercepted-iframe.html": [
|
||||||
|
"79aa49d7287ce90d72d4cb10511e3927d5f0a664",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
"service-workers/service-worker/resources/object-is-not-intercepted-iframe.html": [
|
"service-workers/service-worker/resources/object-is-not-intercepted-iframe.html": [
|
||||||
"412591c247a38b07cde7f42248bc412a50128b54",
|
"412591c247a38b07cde7f42248bc412a50128b54",
|
||||||
"support"
|
"support"
|
||||||
|
@ -597697,11 +597651,11 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"webdriver/tests/element_click/bubbling.py": [
|
"webdriver/tests/element_click/bubbling.py": [
|
||||||
"5437f5ca12b93e9a0087e9b341d63af6bace4ffd",
|
"dde7ca3d7b8ee65222af0044f07087d3b0725bb9",
|
||||||
"wdspec"
|
"wdspec"
|
||||||
],
|
],
|
||||||
"webdriver/tests/element_click/select.py": [
|
"webdriver/tests/element_click/select.py": [
|
||||||
"bbce720456bd6bae72ad256b56b7743be85959f3",
|
"bddc341a0feb0d06e75415b8f98b8e9e0c2a829d",
|
||||||
"wdspec"
|
"wdspec"
|
||||||
],
|
],
|
||||||
"webdriver/tests/element_click/stale.py": [
|
"webdriver/tests/element_click/stale.py": [
|
||||||
|
@ -597761,7 +597715,7 @@
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
"webdriver/tests/execute_script/cyclic.py": [
|
"webdriver/tests/execute_script/cyclic.py": [
|
||||||
"cbebfbd2413ea0b10f547ab66fcc7159898e684a",
|
"9d8a28b94b8cdac88650b675cb00bf21261444e8",
|
||||||
"wdspec"
|
"wdspec"
|
||||||
],
|
],
|
||||||
"webdriver/tests/execute_script/user_prompts.py": [
|
"webdriver/tests/execute_script/user_prompts.py": [
|
||||||
|
@ -597769,7 +597723,7 @@
|
||||||
"wdspec"
|
"wdspec"
|
||||||
],
|
],
|
||||||
"webdriver/tests/fullscreen_window.py": [
|
"webdriver/tests/fullscreen_window.py": [
|
||||||
"817011a8cdff7cfd7e445fb8ecb84e5d91f03993",
|
"67cc7d992d81a176297038f5516c8a9c95018040",
|
||||||
"wdspec"
|
"wdspec"
|
||||||
],
|
],
|
||||||
"webdriver/tests/get_window_rect.py": [
|
"webdriver/tests/get_window_rect.py": [
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
[display-012.xht]
|
[display-012.xht]
|
||||||
type: reftest
|
type: reftest
|
||||||
expected:
|
expected:
|
||||||
if os == "mac": FAIL # Fonts on OSX make this gain an additional pixel of background (also fails on Firefox)
|
if os == "mac": FAIL
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[attachment-local-clipping-color-4.html]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,4 @@
|
||||||
|
[script-html-via-cross-origin-blob-url.sub.html]
|
||||||
|
[Untitled]
|
||||||
|
expected: FAIL
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
[autoplay-with-broken-track.html]
|
||||||
|
expected: TIMEOUT
|
||||||
|
[<video autoplay> with <track> child]
|
||||||
|
expected: TIMEOUT
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Append child writable stream demo</title>
|
||||||
|
|
||||||
|
<script src="transforms/transform-stream-polyfill.js"></script>
|
||||||
|
<script src="transforms/text-encode-transform.js"></script>
|
||||||
|
<script src="transforms/parse-json.js"></script>
|
||||||
|
<script src="transforms/split-stream.js"></script>
|
||||||
|
<script src="resources/highlight.pack.js"></script>
|
||||||
|
<script src="resources/web-animations.min.js"></script>
|
||||||
|
<script src="tags/view-source.js"></script>
|
||||||
|
|
||||||
|
<link rel=stylesheet href="resources/common.css">
|
||||||
|
<link rel=stylesheet href="resources/commits.css">
|
||||||
|
|
||||||
|
<h1>Append child writable stream demo</h1>
|
||||||
|
<a id=back-to-index href=index.html>Back to demo index</a>
|
||||||
|
<view-source></view-source>
|
||||||
|
|
||||||
|
<div id=buttons>
|
||||||
|
<button id="load">Load JSON stream</button>
|
||||||
|
<button id="reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id=target></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
const loadButton = document.querySelector('#load');
|
||||||
|
const resetButton = document.querySelector('#reset');
|
||||||
|
const targetDiv = document.querySelector('#target');
|
||||||
|
const FIELDS = ['Hash', 'Date', 'Author', 'Subject'];
|
||||||
|
const FIELDS_LOWERCASE = FIELDS.map(string => string.toLowerCase());
|
||||||
|
|
||||||
|
function createTable(parentElement) {
|
||||||
|
const table = document.createElement('table');
|
||||||
|
table.id = 'commits';
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
for (const heading of FIELDS) {
|
||||||
|
const th = document.createElement('th');
|
||||||
|
th.textContent = heading;
|
||||||
|
tr.appendChild(th);
|
||||||
|
}
|
||||||
|
table.appendChild(tr);
|
||||||
|
parentElement.appendChild(table);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BEGIN SOURCE TO VIEW
|
||||||
|
function appendChildWritableStream(parentNode) {
|
||||||
|
return new WritableStream({
|
||||||
|
write(chunk) {
|
||||||
|
parentNode.appendChild(chunk);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchThenJSONToDOM() {
|
||||||
|
const jsonToElementTransform = new TransformStream({
|
||||||
|
transform(chunk, controller) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
for (const cell of FIELDS_LOWERCASE) {
|
||||||
|
const td = document.createElement('td');
|
||||||
|
td.textContent = chunk[cell];
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
controller.enqueue(tr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch('data/commits.json',
|
||||||
|
{
|
||||||
|
mode: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache, no-store'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const table = createTable(targetDiv);
|
||||||
|
return response.body
|
||||||
|
.pipeThrough(new TextDecoder())
|
||||||
|
.pipeThrough(splitStream('\n'))
|
||||||
|
.pipeThrough(parseJSON())
|
||||||
|
.pipeThrough(jsonToElementTransform)
|
||||||
|
.pipeTo(appendChildWritableStream(table));
|
||||||
|
}
|
||||||
|
// END SOURCE TO VIEW
|
||||||
|
|
||||||
|
loadButton.onclick = () => {
|
||||||
|
loadButton.disabled = true;
|
||||||
|
setTimeout(fetchThenJSONToDOM, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
resetButton.onclick = () => {
|
||||||
|
targetDiv.innerHTML = '';
|
||||||
|
loadButton.disabled = false;
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Streaming element with backpressure demo</title>
|
||||||
|
|
||||||
|
<script src="transforms/transform-stream-polyfill.js"></script>
|
||||||
|
<script src="transforms/text-encode-transform.js"></script>
|
||||||
|
<script src="tags/streaming-element-backpressure.js"></script>
|
||||||
|
<script src="resources/highlight.pack.js"></script>
|
||||||
|
<script src="resources/web-animations.min.js"></script>
|
||||||
|
<script src="tags/view-source.js"></script>
|
||||||
|
|
||||||
|
<link rel=stylesheet href="resources/common.css">
|
||||||
|
<link rel=stylesheet href="resources/jank-meter.css">
|
||||||
|
<link rel=stylesheet href="resources/commits.css">
|
||||||
|
|
||||||
|
<h1>Streaming element with backpressure demo</h1>
|
||||||
|
<a id=back-to-index href=index.html>Back to demo index</a>
|
||||||
|
<view-source></view-source>
|
||||||
|
|
||||||
|
<div id=buttons>
|
||||||
|
<button id="load">Load HTML stream, applying backpressure</button>
|
||||||
|
<button id="reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id=jank-meter>JANK METER</div>
|
||||||
|
|
||||||
|
<streaming-element-backpressure id=target></streaming-element-backpressure>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
const loadButton = document.querySelector('#load');
|
||||||
|
const resetButton = document.querySelector('#reset');
|
||||||
|
const targetDiv = document.querySelector('#target');
|
||||||
|
|
||||||
|
loadButton.onclick = async () => {
|
||||||
|
loadButton.disabled = true;
|
||||||
|
fetchDirectlyIntoDOM();
|
||||||
|
};
|
||||||
|
|
||||||
|
// BEGIN SOURCE TO VIEW
|
||||||
|
async function fetchDirectlyIntoDOM() {
|
||||||
|
const response = await fetch('data/commits.include',
|
||||||
|
{
|
||||||
|
mode: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache, no-store'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await response.body
|
||||||
|
.pipeThrough(new TextDecoder())
|
||||||
|
.pipeTo(targetDiv.writable);
|
||||||
|
}
|
||||||
|
// END SOURCE TO VIEW
|
||||||
|
|
||||||
|
resetButton.onclick = () => {
|
||||||
|
targetDiv.reset();
|
||||||
|
loadButton.disabled = false;
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Streaming element demo</title>
|
||||||
|
|
||||||
|
<script src="transforms/transform-stream-polyfill.js"></script>
|
||||||
|
<script src="transforms/text-encode-transform.js"></script>
|
||||||
|
<script src="tags/streaming-element.js"></script>
|
||||||
|
<script src="resources/highlight.pack.js"></script>
|
||||||
|
<script src="resources/web-animations.min.js"></script>
|
||||||
|
<script src="tags/view-source.js"></script>
|
||||||
|
|
||||||
|
<link rel=stylesheet href="resources/common.css">
|
||||||
|
<link rel=stylesheet href="resources/jank-meter.css">
|
||||||
|
<link rel=stylesheet href="resources/commits.css">
|
||||||
|
|
||||||
|
<h1>Streaming element demo</h1>
|
||||||
|
<a id=back-to-index href=index.html>Back to demo index</a>
|
||||||
|
<view-source></view-source>
|
||||||
|
|
||||||
|
<div id=buttons>
|
||||||
|
<button id="load">Load HTML stream</button>
|
||||||
|
<button id="reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id=jank-meter>JANK METER</div>
|
||||||
|
|
||||||
|
<streaming-element id=target></streaming-element>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
const loadButton = document.querySelector('#load');
|
||||||
|
const resetButton = document.querySelector('#reset');
|
||||||
|
const targetDiv = document.querySelector('#target');
|
||||||
|
|
||||||
|
// BEGIN SOURCE TO VIEW
|
||||||
|
async function streamDirectlyIntoDOM() {
|
||||||
|
const response = await fetch('data/commits.include',
|
||||||
|
{
|
||||||
|
mode: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache, no-store'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await response.body
|
||||||
|
.pipeThrough(new TextDecoder())
|
||||||
|
.pipeTo(targetDiv.writable);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadButton.onclick = () => {
|
||||||
|
loadButton.disabled = true;
|
||||||
|
streamDirectlyIntoDOM();
|
||||||
|
};
|
||||||
|
// END SOURCE TO VIEW
|
||||||
|
|
||||||
|
resetButton.onclick = () => {
|
||||||
|
targetDiv.reset();
|
||||||
|
loadButton.disabled = false;
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -28,7 +28,7 @@ registerPaint('geometry', class {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
paint(ctx, geom, styleMap) {
|
paint(ctx, geom, styleMap) {
|
||||||
const properties = styleMap.getProperties().sort();
|
const properties = [...styleMap.keys()].sort();
|
||||||
var serializedStrings = [];
|
var serializedStrings = [];
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
const value = styleMap.get(properties[i]);
|
const value = styleMap.get(properties[i]);
|
||||||
|
|
|
@ -29,7 +29,7 @@ registerPaint('geometry', class {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
paint(ctx, geom, styleMap) {
|
paint(ctx, geom, styleMap) {
|
||||||
const properties = styleMap.getProperties().sort();
|
const properties = [...styleMap.keys()].sort();
|
||||||
var serializedStrings = [];
|
var serializedStrings = [];
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
const value = styleMap.get(properties[i]);
|
const value = styleMap.get(properties[i]);
|
||||||
|
|
|
@ -32,7 +32,7 @@ registerPaint('geometry', class {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
paint(ctx, geom, styleMap) {
|
paint(ctx, geom, styleMap) {
|
||||||
const properties = styleMap.getProperties().sort();
|
const properties = [...styleMap.keys()].sort();
|
||||||
var serializedStrings = [];
|
var serializedStrings = [];
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
const value = styleMap.get(properties[i]);
|
const value = styleMap.get(properties[i]);
|
||||||
|
|
|
@ -28,7 +28,7 @@ registerPaint('geometry', class {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
paint(ctx, geom, styleMap) {
|
paint(ctx, geom, styleMap) {
|
||||||
const properties = styleMap.getProperties().sort();
|
const properties = [...styleMap.keys()].sort();
|
||||||
var serializedStrings = [];
|
var serializedStrings = [];
|
||||||
for (let i = 0; i < properties.length; i++) {
|
for (let i = 0; i < properties.length; i++) {
|
||||||
const value = styleMap.get(properties[i]);
|
const value = styleMap.get(properties[i]);
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Reference for position:sticky elements should not affect the flow position of other elements</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.scroller {
|
|
||||||
height: 200px;
|
|
||||||
width: 100px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
background-color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.before {
|
|
||||||
background-color: fuchsia;
|
|
||||||
}
|
|
||||||
|
|
||||||
.after {
|
|
||||||
background-color: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding {
|
|
||||||
height: 450px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="scroller">
|
|
||||||
<div class="before box"></div>
|
|
||||||
<div class="box"></div>
|
|
||||||
<div class="after box"></div>
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see a fuchsia box, a one-box gap, an orange box, and then a green box above.</div>
|
|
|
@ -1,46 +1,58 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<title>position:sticky elements should not affect the flow position of other elements</title>
|
<title>position:sticky elements should not affect the flow position of other elements</title>
|
||||||
<link rel="match" href="position-sticky-inflow-position-ref.html" />
|
|
||||||
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
||||||
<meta name="assert" content="This test checks that position:sticky elements do not affect the flow position of other elements" />
|
<meta name="assert" content="This test checks that position:sticky elements do not affect the flow position of other elements" />
|
||||||
|
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.scroller {
|
.scroller {
|
||||||
|
position: relative;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
overflow-y: scroll;
|
overflow: scroll;
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticky {
|
#sticky {
|
||||||
background-color: green;
|
background-color: green;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 150px;
|
top: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
#before {
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.before {
|
|
||||||
background-color: fuchsia;
|
background-color: fuchsia;
|
||||||
}
|
}
|
||||||
|
|
||||||
.after {
|
#after {
|
||||||
background-color: orange;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.padding {
|
.padding {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="scroller">
|
<div class="scroller">
|
||||||
<div class="before box"></div>
|
<div id="before" class="box"></div>
|
||||||
<div class="sticky box"></div>
|
<div id="sticky" class="box"></div>
|
||||||
<div class="after box"></div>
|
<div id="after" class="box"></div>
|
||||||
<div class="padding"></div>
|
<div class="padding"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>You should see a fuchsia box, a one-box gap, an orange box, and then a green box above.</div>
|
<script>
|
||||||
|
test(() => {
|
||||||
|
// The sticky element is pushed to be stuck 150 pixels from the top.
|
||||||
|
assert_equals(sticky.offsetTop, 150);
|
||||||
|
|
||||||
|
// Neither 'before' or 'after' should be affected by the change in the sticky
|
||||||
|
// element's location.
|
||||||
|
assert_equals(before.offsetTop, 0);
|
||||||
|
assert_equals(after.offsetTop, before.clientHeight + sticky.clientHeight);
|
||||||
|
}, 'sticky offset should not affect the position of other elements.');
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Reference for position:sticky elements should properly interact with margins</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.group {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 180px;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
|
||||||
width: 150px;
|
|
||||||
height: 300px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
position: relative;
|
|
||||||
background-color: green;
|
|
||||||
margin: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding {
|
|
||||||
height: 385px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
document.getElementById('scroller1').scrollTop = 0;
|
|
||||||
document.getElementById('scroller2').scrollTop = 60;
|
|
||||||
document.getElementById('scroller3').scrollTop = 120;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 0;"></div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 50px;"></div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 85px;"></div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
|
@ -1,92 +1,49 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<title>position:sticky elements should properly interact with margins</title>
|
<title>position:sticky elements should properly interact with margins</title>
|
||||||
<link rel="match" href="position-sticky-margins-ref.html" />
|
|
||||||
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
||||||
<meta name="assert" content="position:sticky elements should ignore margins when sticking, but consider them when making sure sticky elements do not escape their containing block" />
|
<meta name="assert" content="position:sticky elements should ignore margins when sticking, but consider them when making sure sticky elements do not escape their containing block" />
|
||||||
|
|
||||||
<style>
|
<script src="/resources/testharness.js"></script>
|
||||||
.group {
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 180px;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
<script src="resources/sticky-util.js"></script>
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 300px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.holder {
|
<body></body>
|
||||||
width: 130px;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
position: sticky;
|
|
||||||
background-color: green;
|
|
||||||
top: 5px;
|
|
||||||
margin: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
left: 15px;
|
|
||||||
position: absolute;
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.padding {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', function() {
|
test(() => {
|
||||||
document.getElementById('scroller1').scrollTop = 0;
|
const elements = setupStickyTest('top', 50);
|
||||||
document.getElementById('scroller2').scrollTop = 60;
|
elements.sticky.style.margin = '15px';
|
||||||
document.getElementById('scroller3').scrollTop = 120;
|
elements.scroller.scrollTop = 100;
|
||||||
});
|
assert_equals(elements.sticky.offsetTop,
|
||||||
|
elements.container.offsetTop + elements.filler.clientHeight + 15);
|
||||||
|
}, 'Before sticking, the margin should be obeyed.');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.margin = '15px';
|
||||||
|
|
||||||
|
elements.scroller.scrollTop = 200;
|
||||||
|
|
||||||
|
// This math cancels to sticky.offsetTop == (scroller.scrollTop + 50), but
|
||||||
|
// for clarity the calculations are left explicit.
|
||||||
|
const nonStickyTopY = elements.container.offsetTop +
|
||||||
|
elements.filler.clientHeight;
|
||||||
|
const targetTopY = elements.scroller.scrollTop + 50;
|
||||||
|
const stickyOffset = targetTopY - nonStickyTopY;
|
||||||
|
|
||||||
|
assert_equals(elements.sticky.offsetTop, nonStickyTopY + stickyOffset);
|
||||||
|
}, 'Whilst stuck, the margin is irrelevant.');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.margin = '15px';
|
||||||
|
|
||||||
|
elements.scroller.scrollTop = 300;
|
||||||
|
|
||||||
|
const maxOffsetInContainer = elements.container.offsetTop +
|
||||||
|
elements.container.clientHeight - elements.sticky.clientHeight;
|
||||||
|
assert_equals(elements.sticky.offsetTop, maxOffsetInContainer - 15);
|
||||||
|
}, 'The margin is taken into account when making sure the sticky element ' +
|
||||||
|
'does not escape its container');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Before sticking, the margin should be obeyed. -->
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 15px;"></div>
|
|
||||||
<div class="holder">
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Whilst stuck, the margin is irrelevant. -->
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 65px;"></div>
|
|
||||||
<div class="holder">
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- The margin does count when making sure the sticky element does not escape
|
|
||||||
its containing block. -->
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 100px;"></div>
|
|
||||||
<div class="holder">
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
<div class="padding"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Reference for position:sticky elements should respect padding on their ancestor overflow element</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.group {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
|
||||||
padding: 20px 0;
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 200px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contents {
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: green;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
document.getElementById('scroller1').scrollTop = 50;
|
|
||||||
document.getElementById('scroller2').scrollTop = 175;
|
|
||||||
document.getElementById('scroller3').scrollTop = 220;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="top: 170px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="top: 195px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="top: 220px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
|
@ -1,106 +1,54 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<title>position:sticky elements should respect padding on their ancestor overflow element</title>
|
<title>position:sticky elements should respect padding on their ancestor overflow element</title>
|
||||||
<link rel="match" href="position-sticky-overflow-padding-ref.html" />
|
|
||||||
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
||||||
<meta name="assert" content="This test checks that position:sticky elements respect padding on their ancestor overflow element" />
|
<meta name="assert" content="This test checks that position:sticky elements respect padding on their ancestor overflow element" />
|
||||||
|
|
||||||
<style>
|
<script src="/resources/testharness.js"></script>
|
||||||
.group {
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
<script src="resources/sticky-util.js"></script>
|
||||||
/* The target sticky position should be offset by this padding. */
|
|
||||||
padding: 20px 0;
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 200px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contents {
|
<body></body>
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prepadding {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.innerpadding {
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: red;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
background-color: green;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', function() {
|
test(() => {
|
||||||
document.getElementById('scroller1').scrollTop = 50;
|
const elements = setupStickyTest('top', 50);
|
||||||
document.getElementById('scroller2').scrollTop = 175;
|
elements.scroller.style.padding = '20px 0';
|
||||||
document.getElementById('scroller3').scrollTop = 220;
|
|
||||||
});
|
// Before sticking; the element isn't within the padding range.
|
||||||
|
elements.scroller.scrollTop = 150;
|
||||||
|
const nonStickyTopY = elements.container.offsetTop +
|
||||||
|
elements.filler.clientHeight;
|
||||||
|
assert_equals(elements.sticky.offsetTop, nonStickyTopY);
|
||||||
|
}, 'A sticky element should not be affected by ancestor padding until it ' +
|
||||||
|
'reaches it');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.top = '0';
|
||||||
|
elements.scroller.style.padding = '20px 0';
|
||||||
|
|
||||||
|
elements.scroller.scrollTop = 200;
|
||||||
|
|
||||||
|
// This math cancels to sticky.offsetTop == (scroller.scrollTop + 50), but
|
||||||
|
// for clarity the calculations are left explicit.
|
||||||
|
const nonStickyTopY = elements.container.offsetTop +
|
||||||
|
elements.filler.clientHeight;
|
||||||
|
const targetTopY = elements.scroller.scrollTop;
|
||||||
|
const stickyOffset = targetTopY - nonStickyTopY;
|
||||||
|
|
||||||
|
assert_equals(elements.sticky.offsetTop, nonStickyTopY + stickyOffset + 20);
|
||||||
|
}, 'A sticky element should be offset by ancestor padding even when stuck');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.top = '0';
|
||||||
|
elements.scroller.style.padding = '20px 0';
|
||||||
|
|
||||||
|
elements.scroller.scrollTop = 315;
|
||||||
|
const maxOffsetInContainer = elements.container.offsetTop +
|
||||||
|
elements.container.clientHeight - elements.sticky.clientHeight;
|
||||||
|
assert_equals(elements.sticky.offsetTop, maxOffsetInContainer);
|
||||||
|
}, 'Ancestor overflow padding does not allow a sticky element to escape its ' +
|
||||||
|
'container');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 170px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="prepadding"></div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="innerpadding"></div>
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 195px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="prepadding"></div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="innerpadding"></div>
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 220px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="prepadding"></div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="innerpadding"></div>
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Reference for position:sticky should operate correctly for the root scroller</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
/* Assumption: 3000px is taller than any user agents test window size. */
|
|
||||||
height: 3000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: green;
|
|
||||||
position: absolute;
|
|
||||||
top: 750px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
window.scrollTo(0, 700);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="indicator box"></div>
|
|
||||||
|
|
||||||
<div style="position: absolute; top: 1000px;">You should see a green box above. No red should be visible.</div>
|
|
|
@ -1,39 +1,31 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<title>position:sticky should operate correctly for the root scroller</title>
|
<title>position:sticky should operate correctly for the root scroller</title>
|
||||||
<link rel="match" href="position-sticky-root-scroller-ref.html" />
|
|
||||||
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
||||||
<meta name="assert" content="This test checks that position:sticky elements work when using the root (document) scroller" />
|
<meta name="assert" content="This test checks that position:sticky elements work when using the root (document) scroller" />
|
||||||
|
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
/* Assumption: 3000px is taller than any user agents test window size. */
|
/* Assumption: 3000px is taller than any user agents test window size. */
|
||||||
height: 3000px;
|
height: 3000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator {
|
#sticky {
|
||||||
background-color: red;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
background-color: green;
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
background-color: green;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<div id="sticky"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', function() {
|
test(() => {
|
||||||
window.scrollTo(0, 700);
|
window.scrollTo(0, 700);
|
||||||
});
|
assert_equals(sticky.offsetTop, 700 + 50);
|
||||||
|
}, 'Sticky elements work with the root (document) scroller');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="indicator box" style="top: 750px;"></div>
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
|
|
||||||
<div style="position: absolute; top: 1000px;">You should see a green box above. No red should be visible.</div>
|
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Reference for transforms on position:sticky elements should apply after sticking</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.group {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 200px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contents {
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: green;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rotated {
|
|
||||||
transform: rotateX(60deg);
|
|
||||||
height: 100px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.perspective {
|
|
||||||
transform: perspective(3px) translateZ(1px);
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
document.getElementById('scroller1').scrollTop = 50;
|
|
||||||
document.getElementById('scroller2').scrollTop = 50;
|
|
||||||
document.getElementById('scroller3').scrollTop = 50;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="height: 100px; top: 75px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="rotated indicator" style="top: 100px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<!-- Required for blending. -->
|
|
||||||
<div class="perspective" style="position: absolute; background: red; top: 100px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="perspective indicator" style="top: 100px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<title>Reference for translations on position:sticky elements should apply after sticking</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.group {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 200px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contents {
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: green;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
document.getElementById('scroller1').scrollTop = 50;
|
|
||||||
document.getElementById('scroller2').scrollTop = 70;
|
|
||||||
document.getElementById('scroller3').scrollTop = 50;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="top: 50px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="top: 50px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="contents">
|
|
||||||
<div class="indicator box" style="top: 200px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
|
@ -1,92 +1,45 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<title>translations on position:sticky elements should apply after sticking</title>
|
<title>translations on position:sticky elements should apply after sticking</title>
|
||||||
<link rel="match" href="position-sticky-transforms-translate-ref.html" />
|
|
||||||
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
||||||
<meta name="assert" content="This test checks that translations on position:sticky elements are carried out on their stuck position" />
|
<meta name="assert" content="This test checks that translations on position:sticky elements are carried out on their stuck position" />
|
||||||
|
|
||||||
<style>
|
<script src="/resources/testharness.js"></script>
|
||||||
.group {
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
<script src="resources/sticky-util.js"></script>
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 200px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contents {
|
<body style="margin: 0;"></body>
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: red;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
background-color: green;
|
|
||||||
position: sticky;
|
|
||||||
top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', function() {
|
test(() => {
|
||||||
document.getElementById('scroller1').scrollTop = 50;
|
const elements = setupStickyTest('top', 50);
|
||||||
document.getElementById('scroller2').scrollTop = 70;
|
elements.sticky.style.transform = 'translateY(-100%)';
|
||||||
document.getElementById('scroller3').scrollTop = 50;
|
elements.scroller.scrollTop = 100;
|
||||||
});
|
// Transforms don't affect offsetTop, so use getBoundingClientRect.
|
||||||
|
assert_equals(elements.sticky.getBoundingClientRect().y,
|
||||||
|
elements.scroller.getBoundingClientRect().y);
|
||||||
|
}, 'Translation transform can move sticky element past sticking point');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.transform = 'translateY(50%)';
|
||||||
|
elements.scroller.scrollTop = 200;
|
||||||
|
// Transforms don't affect offsetTop, so use getBoundingClientRect.
|
||||||
|
const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
|
||||||
|
elements.scroller.getBoundingClientRect().y;
|
||||||
|
assert_equals(stickyElementOffset, 100);
|
||||||
|
}, 'Stuck elements can still be moved via translations');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.container.style.transform = 'translateY(100px)';
|
||||||
|
elements.scroller.scrollTop = 200;
|
||||||
|
// Transforms don't affect offsetTop, so use getBoundingClientRect.
|
||||||
|
// Here the sticky element will originally have stuck at 50px from the top,
|
||||||
|
// but is then 'pulled' downwards by the 100px container transform.
|
||||||
|
const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
|
||||||
|
elements.scroller.getBoundingClientRect().y;
|
||||||
|
assert_equals(stickyElementOffset, 150);
|
||||||
|
}, 'The sticky element should stick before the container is offset by a ' +
|
||||||
|
'translation');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 50px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="container">
|
|
||||||
<div class="sticky box" style="transform: translateY(-100%);"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- The pre-transform sticky is not allowed to escape its containing block. -->
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 50px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="container">
|
|
||||||
<div class="sticky box" style="transform: translateY(-100%);"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- The sticky element should stick before the container is transformed. -->
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="indicator box" style="top: 200px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="container" style="transform: translateY(100px);">
|
|
||||||
<div class="sticky box"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
||||||
|
|
|
@ -1,101 +1,49 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<title>transforms on position:sticky elements should apply after sticking</title>
|
<title>transforms on position:sticky elements should apply after sticking</title>
|
||||||
<link rel="match" href="position-sticky-transforms-ref.html" />
|
|
||||||
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
|
||||||
<meta name="assert" content="This test checks that transforms on position:sticky elements are carried out on their stuck position" />
|
<meta name="assert" content="This test checks that transforms on position:sticky elements are carried out on their stuck position" />
|
||||||
|
|
||||||
<style>
|
<script src="/resources/testharness.js"></script>
|
||||||
.group {
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: 150px;
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroller {
|
<script src="resources/sticky-util.js"></script>
|
||||||
position: relative;
|
|
||||||
width: 100px;
|
|
||||||
height: 200px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contents {
|
<body style="margin: 0;"></body>
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
|
||||||
background-color: red;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky {
|
|
||||||
background-color: green;
|
|
||||||
position: sticky;
|
|
||||||
top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rotated {
|
|
||||||
transform: rotateX(60deg);
|
|
||||||
width: 100%;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.perspective {
|
|
||||||
transform: perspective(3px) translateZ(1px);
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('load', function() {
|
test(() => {
|
||||||
document.getElementById('scroller1').scrollTop = 50;
|
const elements = setupStickyTest('top', 50);
|
||||||
document.getElementById('scroller2').scrollTop = 50;
|
elements.sticky.style.transform = 'scale(2)';
|
||||||
document.getElementById('scroller3').scrollTop = 50;
|
elements.scroller.scrollTop = 200;
|
||||||
});
|
|
||||||
|
// Transforms don't affect offsetTop, so use getBoundingClientRect.
|
||||||
|
// Scaling the sticky element by 2 means its top-y moves (1/2 * height)
|
||||||
|
// upwards, in this case placing it at the top of the viewport.
|
||||||
|
const boundingRect = elements.sticky.getBoundingClientRect();
|
||||||
|
assert_equals(boundingRect.y, elements.scroller.getBoundingClientRect().y);
|
||||||
|
}, 'Scale transforms are carried out on the stuck element position');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.transform = 'rotateX(60deg)';
|
||||||
|
elements.scroller.scrollTop = 200;
|
||||||
|
|
||||||
|
// Transforms don't affect offsetTop, so use getBoundingClientRect.
|
||||||
|
// Rotating around the x-axis essentially 'squashes' it (from the camera's
|
||||||
|
// viewpoint), in this case shifting the offset to 75 rather than 50.
|
||||||
|
const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
|
||||||
|
elements.scroller.getBoundingClientRect().y;
|
||||||
|
assert_equals(stickyElementOffset, 75);
|
||||||
|
}, 'Rotate transforms are carried out on the stuck element position');
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
const elements = setupStickyTest('top', 50);
|
||||||
|
elements.sticky.style.transform = 'perspective(3px) translateZ(1px)';
|
||||||
|
elements.scroller.scrollTop = 200;
|
||||||
|
|
||||||
|
// Transforms don't affect offsetTop, so use getBoundingClientRect.
|
||||||
|
const stickyElementOffset = elements.sticky.getBoundingClientRect().y -
|
||||||
|
elements.scroller.getBoundingClientRect().y;
|
||||||
|
assert_equals(stickyElementOffset, 25);
|
||||||
|
}, 'Perspective transforms are carried out on the stuck element position');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller1" class="scroller">
|
|
||||||
<div class="indicator box" style="height: 100px; top: 75px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="container">
|
|
||||||
<div class="sticky box" style="transform: scale(2);"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller2" class="scroller">
|
|
||||||
<div class="rotated indicator" style="top: 100px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="container" style="height: 250px;">
|
|
||||||
<div class="rotated sticky"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="group">
|
|
||||||
<div id="scroller3" class="scroller">
|
|
||||||
<div class="perspective indicator" style="top: 100px;"></div>
|
|
||||||
<div class="contents">
|
|
||||||
<div class="container">
|
|
||||||
<div class="perspective sticky"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>You should see three green boxes above. No red should be visible.</div>
|
|
||||||
|
|
|
@ -15,11 +15,9 @@
|
||||||
const target = document.getElementById('target');
|
const target = document.getElementById('target');
|
||||||
const styleMap = target.computedStyleMap();
|
const styleMap = target.computedStyleMap();
|
||||||
|
|
||||||
// FIXME(crbug.com/788904): Test currently fails because the 'content' property returns
|
|
||||||
// incorrect initial CSS value.
|
|
||||||
test(() => {
|
test(() => {
|
||||||
const computedStyle = [...getComputedStyle(target)].sort();
|
const computedStyle = [...getComputedStyle(target)].sort();
|
||||||
const properties = styleMap.getProperties();
|
const properties = [...styleMap.keys()];
|
||||||
|
|
||||||
// Two extra entries for custom properties
|
// Two extra entries for custom properties
|
||||||
assert_equals(properties.length, computedStyle.length + 2);
|
assert_equals(properties.length, computedStyle.length + 2);
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>StylePropertyMap.getProperties tests</title>
|
|
||||||
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-getproperties">
|
|
||||||
<script src="/resources/testharness.js"></script>
|
|
||||||
<script src="/resources/testharnessreport.js"></script>
|
|
||||||
<script src="../../resources/testhelper.js"></script>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
test(t => {
|
|
||||||
const styleMap = createComputedStyleMap(t, '--A: A; width: 0px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;');
|
|
||||||
const expectedProperties = [...getComputedStyle(document.body)].sort().concat('--A', '--B', '--C');
|
|
||||||
assert_array_equals(styleMap.getProperties(), expectedProperties);
|
|
||||||
}, 'StylePropertyMap.getProperties returns property names in correct order');
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -31,7 +31,7 @@ const target = document.getElementById('target');
|
||||||
const styleMap = document.styleSheets[0].rules[0].styleMap;
|
const styleMap = document.styleSheets[0].rules[0].styleMap;
|
||||||
|
|
||||||
test(() => {
|
test(() => {
|
||||||
const properties = styleMap.getProperties();
|
const properties = [...styleMap.keys()];
|
||||||
assert_array_equals(properties, ['height', 'transition-duration', 'width', '--foo']);
|
assert_array_equals(properties, ['height', 'transition-duration', 'width', '--foo']);
|
||||||
}, 'Declared StylePropertyMap only contains properties in the style rule');
|
}, 'Declared StylePropertyMap only contains properties in the style rule');
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>StylePropertyMap.getProperties tests</title>
|
|
||||||
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-getproperties">
|
|
||||||
<script src="/resources/testharness.js"></script>
|
|
||||||
<script src="/resources/testharnessreport.js"></script>
|
|
||||||
<script src="../../resources/testhelper.js"></script>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
test(t => {
|
|
||||||
const styleMap = createDeclaredStyleMap(t, '');
|
|
||||||
assert_array_equals(styleMap.getProperties(), []);
|
|
||||||
}, 'Calling StylePropertyMap.getProperties on an empty property model returns a zero-length array');
|
|
||||||
|
|
||||||
test(t => {
|
|
||||||
const styleMap = createDeclaredStyleMap(t, '--A: A; width: 0px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;');
|
|
||||||
assert_array_equals(styleMap.getProperties(),
|
|
||||||
['color', 'transition-duration', 'width', '--A', '--B', '--C']);
|
|
||||||
}, 'StylePropertyMap.getProperties returns CSS properties in alphabetical order then custom properties by codepoint');
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -1,23 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>StylePropertyMap.getProperties tests</title>
|
|
||||||
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-getproperties">
|
|
||||||
<script src="/resources/testharness.js"></script>
|
|
||||||
<script src="/resources/testharnessreport.js"></script>
|
|
||||||
<script src="../../resources/testhelper.js"></script>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
test(t => {
|
|
||||||
const styleMap = createInlineStyleMap(t, '');
|
|
||||||
assert_array_equals(styleMap.getProperties(), []);
|
|
||||||
}, 'Calling StylePropertyMap.getProperties on an empty property model returns a zero-length array');
|
|
||||||
|
|
||||||
test(t => {
|
|
||||||
const styleMap = createInlineStyleMap(t, '--A: A; width: 0px; --C: C; transition-duration: 1s, 2s; color: red; --B: B;');
|
|
||||||
assert_array_equals(styleMap.getProperties(),
|
|
||||||
['color', 'transition-duration', 'width', '--A', '--B', '--C']);
|
|
||||||
}, 'StylePropertyMap.getProperties returns property names in correct order');
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -16,6 +16,21 @@ function assert_is_equal_with_range_handling(input, result) {
|
||||||
assert_style_value_equals(result, input);
|
assert_style_value_equals(result, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gCssWideKeywordsExamples = [
|
||||||
|
{
|
||||||
|
description: 'initial keyword',
|
||||||
|
input: new CSSKeywordValue('initial')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'inherit keyword',
|
||||||
|
input: new CSSKeywordValue('initial')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'unset keyword',
|
||||||
|
input: new CSSKeywordValue('initial')
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const gTestSyntaxExamples = {
|
const gTestSyntaxExamples = {
|
||||||
'<length>': {
|
'<length>': {
|
||||||
description: 'a length',
|
description: 'a length',
|
||||||
|
@ -149,26 +164,28 @@ function testPropertyValid(propertyName, examples, specified, computed, descript
|
||||||
|
|
||||||
// specified style
|
// specified style
|
||||||
const specifiedResult = element.attributeStyleMap.get(propertyName);
|
const specifiedResult = element.attributeStyleMap.get(propertyName);
|
||||||
|
assert_not_equals(specifiedResult, null,
|
||||||
|
'Specified value must not be null');
|
||||||
|
assert_true(specifiedResult instanceof CSSStyleValue,
|
||||||
|
'Specified value must be a CSSStyleValue');
|
||||||
|
|
||||||
if (specified || example.defaultSpecified) {
|
if (specified || example.defaultSpecified) {
|
||||||
(specified || example.defaultSpecified)(example.input, specifiedResult);
|
(specified || example.defaultSpecified)(example.input, specifiedResult);
|
||||||
} else {
|
} else {
|
||||||
assert_not_equals(specifiedResult, null,
|
|
||||||
'Specified value must not be null');
|
|
||||||
assert_true(specifiedResult instanceof CSSStyleValue,
|
|
||||||
'Specified value must be a CSSStyleValue');
|
|
||||||
assert_style_value_equals(specifiedResult, example.input,
|
assert_style_value_equals(specifiedResult, example.input,
|
||||||
`Setting ${example.description} and getting its specified value`);
|
`Setting ${example.description} and getting its specified value`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// computed style
|
// computed style
|
||||||
const computedResult = element.computedStyleMap().get(propertyName);
|
const computedResult = element.computedStyleMap().get(propertyName);
|
||||||
|
assert_not_equals(computedResult, null,
|
||||||
|
'Computed value must not be null');
|
||||||
|
assert_true(computedResult instanceof CSSStyleValue,
|
||||||
|
'Computed value must be a CSSStyleValue');
|
||||||
|
|
||||||
if (computed || example.defaultComputed) {
|
if (computed || example.defaultComputed) {
|
||||||
(computed || example.defaultComputed)(example.input, computedResult);
|
(computed || example.defaultComputed)(example.input, computedResult);
|
||||||
} else {
|
} else {
|
||||||
assert_not_equals(computedResult, null,
|
|
||||||
'Computed value must not be null');
|
|
||||||
assert_true(computedResult instanceof CSSStyleValue,
|
|
||||||
'Computed value must be a CSSStyleValue');
|
|
||||||
assert_style_value_equals(computedResult, example.input,
|
assert_style_value_equals(computedResult, example.input,
|
||||||
`Setting ${example.description} and getting its computed value`);
|
`Setting ${example.description} and getting its computed value`);
|
||||||
}
|
}
|
||||||
|
@ -220,6 +237,13 @@ function createKeywordExample(keyword) {
|
||||||
function runPropertyTests(propertyName, testCases) {
|
function runPropertyTests(propertyName, testCases) {
|
||||||
let syntaxTested = new Set();
|
let syntaxTested = new Set();
|
||||||
|
|
||||||
|
// Every property should at least support CSS-wide keywords.
|
||||||
|
testPropertyValid(propertyName,
|
||||||
|
gCssWideKeywordsExamples,
|
||||||
|
null, // should be as specified
|
||||||
|
() => {}, // could be anything
|
||||||
|
'CSS-wide keywords');
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
for (const testCase of testCases) {
|
||||||
// Retrieve test examples for this test case's syntax. If the syntax
|
// Retrieve test examples for this test case's syntax. If the syntax
|
||||||
// looks like a keyword, then create an example on the fly.
|
// looks like a keyword, then create an example on the fly.
|
||||||
|
|
|
@ -42,10 +42,17 @@ test(function() {
|
||||||
|
|
||||||
test(function() {
|
test(function() {
|
||||||
var serializer = new XMLSerializer();
|
var serializer = new XMLSerializer();
|
||||||
var root = createXmlDoc().documentElement;
|
var parser = new DOMParser();
|
||||||
root.firstChild.setAttribute('attr1', 'value1\tvalue2\r\n');
|
var root = parser.parseFromString('<root />', 'text/xml').documentElement;
|
||||||
var xmlString = serializer.serializeToString(root);
|
root.setAttribute('attr', '\t');
|
||||||
assert_equals(xmlString, '<root><child1 attr1="value1	value2
">value1</child1></root>');
|
assert_in_array(serializer.serializeToString(root), [
|
||||||
|
'<root attr="	"/>', '<root attr="	"/>']);
|
||||||
|
root.setAttribute('attr', '\n');
|
||||||
|
assert_in_array(serializer.serializeToString(root), [
|
||||||
|
'<root attr="
"/>', '<root attr=" "/>']);
|
||||||
|
root.setAttribute('attr', '\r');
|
||||||
|
assert_in_array(serializer.serializeToString(root), [
|
||||||
|
'<root attr="
"/>', '<root attr=" "/>']);
|
||||||
}, 'check XMLSerializer.serializeToString escapes attribute values for roundtripping');
|
}, 'check XMLSerializer.serializeToString escapes attribute values for roundtripping');
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
|
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
|
||||||
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
|
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
|
||||||
same_origin_src;
|
same_origin_src;
|
||||||
const header = 'Default "picture-in-picture" feature policy ["self"]';
|
const header = 'Default "picture-in-picture" feature policy [*]';
|
||||||
|
|
||||||
async_test(t => {
|
async_test(t => {
|
||||||
isPictureInPictureAllowed().then(t.step_func_done((result) => {
|
isPictureInPictureAllowed().then(t.step_func_done((result) => {
|
||||||
|
@ -26,8 +26,8 @@
|
||||||
|
|
||||||
async_test(t => {
|
async_test(t => {
|
||||||
test_feature_availability('picture-in-picture', t, cross_origin_src,
|
test_feature_availability('picture-in-picture', t, cross_origin_src,
|
||||||
expect_feature_unavailable_default,);
|
expect_feature_available_default,);
|
||||||
}, header + ' disallows cross-origin iframes.');
|
}, header + ' allows cross-origin iframes.');
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
# Tests related to Cross-Origin Resource Blocking (CORB).
|
# Tests related to Cross-Origin Resource Blocking (CORB).
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
This directory contains tests related to the
|
This directory contains tests related to the
|
||||||
[Cross-Origin Resource Blocking (CORB)](https://chromium.googlesource.com/chromium/src/+/master/content/browser/loader/cross_origin_read_blocking_explainer.md)
|
[Cross-Origin Resource Blocking (CORB)](https://chromium.googlesource.com/chromium/src/+/master/content/browser/loader/cross_origin_read_blocking_explainer.md)
|
||||||
algorithm.
|
algorithm.
|
||||||
|
|
||||||
|
The tests in this directory interact with various, random features,
|
||||||
|
but the tests have been grouped together into the `fetch/corb` directory,
|
||||||
|
because all of these tests verify behavior that is important to the CORB
|
||||||
|
algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
### Disclaimer: CORB is not standardized yet
|
||||||
|
|
||||||
Note that CORB is currently in very early stages of standardization path. At
|
Note that CORB is currently in very early stages of standardization path. At
|
||||||
the same time, some tests in this directory (e.g.
|
the same time, some tests in this directory (e.g.
|
||||||
`css-with-json-parser-breaker`) cover behavior spec-ed outside of CORB (making
|
`css-with-json-parser-breaker`) cover behavior spec-ed outside of CORB (making
|
||||||
|
@ -30,7 +40,35 @@ CORB is enabled. In practice this means that:
|
||||||
`third_party/WebKit/LayoutTests/FlagExpectations/site-per-process` file.
|
`third_party/WebKit/LayoutTests/FlagExpectations/site-per-process` file.
|
||||||
* Such tests may fail in other browsers.
|
* Such tests may fail in other browsers.
|
||||||
|
|
||||||
The tests in this directory interact with various, random features,
|
|
||||||
but the tests have been grouped together into the `fetch/corb` directory,
|
### Limitations of WPT test coverage
|
||||||
because all of these tests verify behavior that is important to the CORB
|
|
||||||
algorithm.
|
CORB is a defense-in-depth and in general should not cause changes in behavior
|
||||||
|
that can be observed by web features or by end users. This makes CORB difficult
|
||||||
|
or even impossible to test via WPT.
|
||||||
|
|
||||||
|
WPT tests can cover the following:
|
||||||
|
|
||||||
|
* Helping verify CORB has no observable impact in specific scenarios.
|
||||||
|
Examples:
|
||||||
|
* image rendering of (an empty response of) a html document blocked by CORB
|
||||||
|
should be indistinguishable from rendering such html document without CORB -
|
||||||
|
`img-html-correctly-labeled.sub.html`
|
||||||
|
* CORB shouldn't block responses that don't sniff as a CORB-protected document
|
||||||
|
type - `img-png-mislabeled-as-html.sub.html`
|
||||||
|
* Helping document cases where CORB causes observable changes in behavior.
|
||||||
|
Examples:
|
||||||
|
* blocking of nosniff images labeled as non-image, CORB-protected
|
||||||
|
Content-Type - `img-png-mislabeled-as-html-nosniff.tentative.sub.html`
|
||||||
|
* blocking of CORB-protected documents can prevent triggering
|
||||||
|
syntax errors in scripts -
|
||||||
|
`script-html-via-cross-origin-blob-url.tentative.sub.html`
|
||||||
|
|
||||||
|
Examples of aspects that WPT tests cannot cover (these aspects have to be
|
||||||
|
covered in other, browser-specific tests):
|
||||||
|
* Verifying that CORB doesn't affect things that are only indirectly
|
||||||
|
observable by the web (like
|
||||||
|
[prefetch](https://html.spec.whatwg.org/#link-type-prefetch).
|
||||||
|
* Verifying that CORB strips non-safe-listed headers of blocked responses.
|
||||||
|
* Verifying that CORB blocks responses before they reach the process hosting
|
||||||
|
a cross-origin execution context.
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script>
|
||||||
|
fetch('html-correctly-labeled.html')
|
||||||
|
.then(response => response.blob())
|
||||||
|
.then(blob => {
|
||||||
|
let msg = { blob_size: blob.size,
|
||||||
|
blob_type: blob.type,
|
||||||
|
blob_url: URL.createObjectURL(blob) };
|
||||||
|
window.parent.postMessage(msg, '*');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
let msg = { error: error };
|
||||||
|
window.parent.postMessage(msg, '*');
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- Test verifies that cross-origin blob URIs are blocked both with and
|
||||||
|
without CORB.
|
||||||
|
-->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<div id=log></div>
|
||||||
|
<script>
|
||||||
|
async_test(function(t) {
|
||||||
|
function step1_createSubframe() {
|
||||||
|
addEventListener("message", function(e) {
|
||||||
|
t.step(function() { step2_processSubframeMsg(e.data); })
|
||||||
|
});
|
||||||
|
var subframe = document.createElement("iframe")
|
||||||
|
// www1 is cross-origin, to ensure that the received blob will be cross-origin.
|
||||||
|
subframe.src = 'http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html';
|
||||||
|
document.body.appendChild(subframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
function step2_processSubframeMsg(msg) {
|
||||||
|
assert_not_exists(msg, 'error');
|
||||||
|
assert_equals(msg.blob_type, 'text/html');
|
||||||
|
assert_equals(msg.blob_size, 147);
|
||||||
|
|
||||||
|
// With and without CORB loading of a cross-origin blob should be blocked
|
||||||
|
// (this is verified by expecting |script.onerror|, but not |script.onload|
|
||||||
|
// below).
|
||||||
|
var script = document.createElement("script")
|
||||||
|
script.src = msg.blob_url;
|
||||||
|
script.onerror = t.step_func_done(function(){})
|
||||||
|
script.onload = t.unreached_func("Unexpected load event")
|
||||||
|
document.body.appendChild(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
step1_createSubframe();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="help" href="https://html.spec.whatwg.org/multipage/media.html#text-track-model">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/common/media.js"></script>
|
||||||
|
<div id="log"></div>
|
||||||
|
<script>
|
||||||
|
// Media elements have a "list of pending text tracks" which should be populated
|
||||||
|
// with text tracks with readyState "loading". When the text track src is
|
||||||
|
// invalid or points to a non-existent resource, it shouldn't be possible to
|
||||||
|
// block the media element's readyState indefinitely.
|
||||||
|
function t(trackSrc) {
|
||||||
|
const track = document.createElement('track');
|
||||||
|
track.src = trackSrc;
|
||||||
|
track.default = true;
|
||||||
|
async_test(t => {
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.autoplay = true;
|
||||||
|
video.controls = true; // for visual inspection, not part of test
|
||||||
|
video.src = getVideoURI('/media/movie_5');
|
||||||
|
video.appendChild(track);
|
||||||
|
document.body.appendChild(video);
|
||||||
|
// The playing event isn't used because it's fired in Safari even when the
|
||||||
|
// playback doesn't actually start.
|
||||||
|
video.ontimeupdate = t.step_func(() => {
|
||||||
|
if (video.currentTime > 0)
|
||||||
|
t.done();
|
||||||
|
});
|
||||||
|
}, `<video autoplay> with ${track.outerHTML} child`);
|
||||||
|
}
|
||||||
|
t("invalid://url");
|
||||||
|
t("404");
|
||||||
|
t("");
|
||||||
|
</script>
|
|
@ -34,7 +34,7 @@ promise_test(t => {
|
||||||
.then(result => {
|
.then(result => {
|
||||||
assert_equals(result, 'request for embedded content was not intercepted');
|
assert_equals(result, 'request for embedded content was not intercepted');
|
||||||
});
|
});
|
||||||
}, 'requests for EMBED elements should not be intercepted by service workers');
|
}, 'requests for EMBED elements of embedded HTML content should not be intercepted by service workers');
|
||||||
|
|
||||||
promise_test(t => {
|
promise_test(t => {
|
||||||
let frame;
|
let frame;
|
||||||
|
@ -47,5 +47,32 @@ promise_test(t => {
|
||||||
.then(result => {
|
.then(result => {
|
||||||
assert_equals(result, 'request for embedded content was not intercepted');
|
assert_equals(result, 'request for embedded content was not intercepted');
|
||||||
});
|
});
|
||||||
}, 'requests for OBJECT elements should not be intercepted by service workers');
|
}, 'requests for OBJECT elements of embedded HTML content should not be intercepted by service workers');
|
||||||
|
|
||||||
|
promise_test(t => {
|
||||||
|
let frame;
|
||||||
|
return with_iframe('resources/embed-image-is-not-intercepted-iframe.html')
|
||||||
|
.then(f => {
|
||||||
|
frame = f;
|
||||||
|
t.add_cleanup(() => { frame.remove(); });
|
||||||
|
return frame.contentWindow.test_promise;
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
assert_equals(result, 'request was not intercepted');
|
||||||
|
});
|
||||||
|
}, 'requests for EMBED elements of an image should not be intercepted by service workers');
|
||||||
|
|
||||||
|
promise_test(t => {
|
||||||
|
let frame;
|
||||||
|
return with_iframe('resources/object-image-is-not-intercepted-iframe.html')
|
||||||
|
.then(f => {
|
||||||
|
frame = f;
|
||||||
|
t.add_cleanup(() => { frame.remove(); });
|
||||||
|
return frame.contentWindow.test_promise;
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
assert_equals(result, 'request was not intercepted');
|
||||||
|
});
|
||||||
|
}, 'requests for OBJECT elements of an image should not be intercepted by service workers');
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,10 @@ def main(request, response):
|
||||||
|
|
||||||
headers = [("X-Request-Method", request.method),
|
headers = [("X-Request-Method", request.method),
|
||||||
("X-Request-Content-Length", request.headers.get("Content-Length", "NO")),
|
("X-Request-Content-Length", request.headers.get("Content-Length", "NO")),
|
||||||
("X-Request-Content-Type", request.headers.get("Content-Type", "NO"))]
|
("X-Request-Content-Type", request.headers.get("Content-Type", "NO")),
|
||||||
|
|
||||||
|
# Avoid any kind of content sniffing on the response.
|
||||||
|
("Content-Type", "text/plain")]
|
||||||
|
|
||||||
content = request.body
|
content = request.body
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,11 @@
|
||||||
self.addEventListener('fetch', e => {
|
self.addEventListener('fetch', e => {
|
||||||
if (e.request.url.indexOf('embedded-content-from-server.html') != -1) {
|
if (e.request.url.indexOf('embedded-content-from-server.html') != -1) {
|
||||||
e.respondWith(fetch('embedded-content-from-service-worker.html'));
|
e.respondWith(fetch('embedded-content-from-service-worker.html'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.request.url.indexOf('green.png') != -1) {
|
||||||
|
e.respondWith(Promise.reject('network error to show interception occurred'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>iframe for embed-and-object-are-not-intercepted test</title>
|
||||||
|
<body>
|
||||||
|
<embed type="image/png" src="/images/green.png"></embed>
|
||||||
|
<script>
|
||||||
|
// Our parent (the root frame of the test) will examine this to get the result.
|
||||||
|
var test_promise = new Promise(resolve => {
|
||||||
|
if (!navigator.serviceWorker.controller)
|
||||||
|
resolve('FAIL: this iframe is not controlled');
|
||||||
|
|
||||||
|
const elem = document.querySelector('embed');
|
||||||
|
elem.addEventListener('load', e => {
|
||||||
|
resolve('request was not intercepted');
|
||||||
|
});
|
||||||
|
elem.addEventListener('error', e => {
|
||||||
|
resolve('FAIL: request was intercepted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
|
@ -15,4 +15,3 @@ var test_promise = new Promise(resolve => {
|
||||||
|
|
||||||
<embed src="embedded-content-from-server.html"></embed>
|
<embed src="embedded-content-from-server.html"></embed>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>iframe for embed-and-object-are-not-intercepted test</title>
|
||||||
|
<body>
|
||||||
|
<object type="image/png" data="/images/green.png"></embed>
|
||||||
|
<script>
|
||||||
|
// Our parent (the root frame of the test) will examine this to get the result.
|
||||||
|
var test_promise = new Promise(resolve => {
|
||||||
|
if (!navigator.serviceWorker.controller)
|
||||||
|
resolve('FAIL: this iframe is not controlled');
|
||||||
|
|
||||||
|
const elem = document.querySelector('object');
|
||||||
|
elem.addEventListener('load', e => {
|
||||||
|
resolve('request was not intercepted');
|
||||||
|
});
|
||||||
|
elem.addEventListener('error', e => {
|
||||||
|
resolve('FAIL: request was intercepted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
|
@ -8,7 +8,7 @@ sys.path.insert(0, os.path.join(here))
|
||||||
sys.path.insert(0, os.path.join(here, "six"))
|
sys.path.insert(0, os.path.join(here, "six"))
|
||||||
sys.path.insert(0, os.path.join(here, "html5lib"))
|
sys.path.insert(0, os.path.join(here, "html5lib"))
|
||||||
sys.path.insert(0, os.path.join(here, "wptserve"))
|
sys.path.insert(0, os.path.join(here, "wptserve"))
|
||||||
sys.path.insert(0, os.path.join(here, "pywebsocket", "src"))
|
sys.path.insert(0, os.path.join(here, "pywebsocket"))
|
||||||
sys.path.insert(0, os.path.join(here, "third_party", "attrs", "src"))
|
sys.path.insert(0, os.path.join(here, "third_party", "attrs", "src"))
|
||||||
sys.path.insert(0, os.path.join(here, "third_party", "funcsigs"))
|
sys.path.insert(0, os.path.join(here, "third_party", "funcsigs"))
|
||||||
sys.path.insert(0, os.path.join(here, "third_party", "pluggy"))
|
sys.path.insert(0, os.path.join(here, "third_party", "pluggy"))
|
||||||
|
|
|
@ -91,6 +91,8 @@ class Manifest(object):
|
||||||
hash_changed = True
|
hash_changed = True
|
||||||
else:
|
else:
|
||||||
new_type, manifest_items = old_type, self._data[old_type][rel_path]
|
new_type, manifest_items = old_type, self._data[old_type][rel_path]
|
||||||
|
if old_type == "reftest" and new_type != old_type:
|
||||||
|
reftest_changes = True
|
||||||
else:
|
else:
|
||||||
new_type, manifest_items = source_file.manifest_items()
|
new_type, manifest_items = source_file.manifest_items()
|
||||||
|
|
||||||
|
|
|
@ -232,6 +232,28 @@ def test_reftest_computation_chain_update_remove():
|
||||||
assert list(m) == [("reftest", test2.path, {test2})]
|
assert list(m) == [("reftest", test2.path, {test2})]
|
||||||
|
|
||||||
|
|
||||||
|
def test_reftest_computation_chain_update_test_type():
|
||||||
|
m = manifest.Manifest()
|
||||||
|
|
||||||
|
s1 = SourceFileWithTest("test", "0"*40, item.RefTest, [("/test-ref", "==")])
|
||||||
|
|
||||||
|
assert m.update([s1]) is True
|
||||||
|
|
||||||
|
test1 = s1.manifest_items()[1][0]
|
||||||
|
|
||||||
|
assert list(m) == [("reftest", test1.path, {test1})]
|
||||||
|
|
||||||
|
# test becomes a testharness test (hash change because that is determined
|
||||||
|
# based on the file contents). The updated manifest should not includes the
|
||||||
|
# old reftest.
|
||||||
|
s2 = SourceFileWithTest("test", "1"*40, item.TestharnessTest)
|
||||||
|
assert m.update([s2]) is True
|
||||||
|
|
||||||
|
test2 = s2.manifest_items()[1][0]
|
||||||
|
|
||||||
|
assert list(m) == [("testharness", test2.path, {test2})]
|
||||||
|
|
||||||
|
|
||||||
def test_iterpath():
|
def test_iterpath():
|
||||||
m = manifest.Manifest()
|
m = manifest.Manifest()
|
||||||
|
|
||||||
|
|
11
tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING
Normal file
11
tests/wpt/web-platform-tests/tools/pywebsocket/CONTRIBUTING
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
For instructions for contributing code, please read:
|
||||||
|
https://github.com/google/pywebsocket/wiki/CodeReviewInstruction
|
||||||
|
|
||||||
|
You must complete the Individual Contributor License Agreement.
|
||||||
|
https://cla.developers.google.com/about/google-individual
|
||||||
|
You can do this online, and it only takes a minute.
|
||||||
|
|
||||||
|
If you are contributing on behalf of a corporation, you must fill out the
|
||||||
|
Corporate Contributor License Agreement
|
||||||
|
https://cla.developers.google.com/about/google-corporate
|
||||||
|
and send it to us as described on that page.
|
8
tests/wpt/web-platform-tests/tools/pywebsocket/README.md
Normal file
8
tests/wpt/web-platform-tests/tools/pywebsocket/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
# pywebsocket #
|
||||||
|
|
||||||
|
The pywebsocket project aims to provide a [WebSocket](https://tools.ietf.org/html/rfc6455) standalone server and a WebSocket extension for [Apache HTTP Server](https://httpd.apache.org/), mod\_pywebsocket.
|
||||||
|
|
||||||
|
pywebsocket is intended for **testing** or **experimental** purposes.
|
||||||
|
|
||||||
|
Please see [Wiki](../../wiki) for more details.
|
|
@ -52,65 +52,35 @@ function getConfig() {
|
||||||
// If the size of each message is small, send/receive multiple messages
|
// If the size of each message is small, send/receive multiple messages
|
||||||
// until the sum of sizes reaches this threshold.
|
// until the sum of sizes reaches this threshold.
|
||||||
minTotal: getIntFromInput('mintotal'),
|
minTotal: getIntFromInput('mintotal'),
|
||||||
multipliers: getIntArrayFromInput('multipliers'),
|
multipliers: getFloatArrayFromInput('multipliers'),
|
||||||
verifyData: getBoolFromCheckBox('verifydata')
|
verifyData: getBoolFromCheckBox('verifydata'),
|
||||||
|
addToLog: addToLog,
|
||||||
|
addToSummary: addToSummary,
|
||||||
|
measureValue: measureValue,
|
||||||
|
notifyAbort: notifyAbort
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var worker = new Worker('benchmark.js');
|
|
||||||
worker.onmessage = onMessage;
|
|
||||||
|
|
||||||
function onSendBenchmark() {
|
function onSendBenchmark() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'sendBenchmark', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
sendBenchmark(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReceiveBenchmark() {
|
function onReceiveBenchmark() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'receiveBenchmark', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
receiveBenchmark(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBatchBenchmark() {
|
function onBatchBenchmark() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'batchBenchmark', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
batchBenchmark(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStop() {
|
function onStop() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'stop', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
stop(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
addressBox = document.getElementById('address');
|
addressBox = document.getElementById('address');
|
||||||
logBox = document.getElementById('log');
|
logBox = document.getElementById('log');
|
||||||
|
@ -128,6 +98,8 @@ function init() {
|
||||||
if (!('WebSocket' in window)) {
|
if (!('WebSocket' in window)) {
|
||||||
addToLog('WebSocket is not available');
|
addToLog('WebSocket is not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initWorker('WebSocket', '');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
|
@ -32,7 +32,7 @@ function destroyAllSockets() {
|
||||||
sockets = [];
|
sockets = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendBenchmarkStep(size, config) {
|
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||||
timerID = null;
|
timerID = null;
|
||||||
|
|
||||||
var totalSize = 0;
|
var totalSize = 0;
|
||||||
|
@ -41,6 +41,7 @@ function sendBenchmarkStep(size, config) {
|
||||||
var onMessageHandler = function(event) {
|
var onMessageHandler = function(event) {
|
||||||
if (!verifyAcknowledgement(config, event.data, size)) {
|
if (!verifyAcknowledgement(config, event.data, size)) {
|
||||||
destroyAllSockets();
|
destroyAllSockets();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +51,8 @@ function sendBenchmarkStep(size, config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||||
|
isWarmUp);
|
||||||
|
|
||||||
runNextTask(config);
|
runNextTask(config);
|
||||||
};
|
};
|
||||||
|
@ -89,7 +91,7 @@ function sendBenchmarkStep(size, config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveBenchmarkStep(size, config) {
|
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||||
timerID = null;
|
timerID = null;
|
||||||
|
|
||||||
var totalSize = 0;
|
var totalSize = 0;
|
||||||
|
@ -101,12 +103,14 @@ function receiveBenchmarkStep(size, config) {
|
||||||
config.addToLog('Expected ' + size + 'B but received ' +
|
config.addToLog('Expected ' + size + 'B but received ' +
|
||||||
bytesReceived + 'B');
|
bytesReceived + 'B');
|
||||||
destroyAllSockets();
|
destroyAllSockets();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) {
|
if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) {
|
||||||
config.addToLog('Response verification failed');
|
config.addToLog('Response verification failed');
|
||||||
destroyAllSockets();
|
destroyAllSockets();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +120,8 @@ function receiveBenchmarkStep(size, config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||||
|
isWarmUp);
|
||||||
|
|
||||||
runNextTask(config);
|
runNextTask(config);
|
||||||
};
|
};
|
||||||
|
@ -153,12 +158,11 @@ function createSocket(config) {
|
||||||
};
|
};
|
||||||
socket.onclose = function(event) {
|
socket.onclose = function(event) {
|
||||||
config.addToLog('Closed');
|
config.addToLog('Closed');
|
||||||
|
config.notifyAbort();
|
||||||
};
|
};
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasks = [];
|
|
||||||
|
|
||||||
function startBenchmark(config) {
|
function startBenchmark(config) {
|
||||||
clearTimeout(timerID);
|
clearTimeout(timerID);
|
||||||
destroyAllSockets();
|
destroyAllSockets();
|
||||||
|
@ -180,24 +184,6 @@ function startBenchmark(config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runNextTask(config) {
|
|
||||||
var task = tasks.shift();
|
|
||||||
if (task == undefined) {
|
|
||||||
config.addToLog('Finished');
|
|
||||||
destroyAllSockets();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerID = setTimeout(task, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLegendString(config) {
|
|
||||||
var legend = ''
|
|
||||||
if (config.printSize)
|
|
||||||
legend = 'Message size in KiB, Time/message in ms, ';
|
|
||||||
legend += 'Speed in kB/s';
|
|
||||||
return legend;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigString(config) {
|
function getConfigString(config) {
|
||||||
return '(WebSocket' +
|
return '(WebSocket' +
|
||||||
', ' + (typeof importScripts !== "undefined" ? 'Worker' : 'Main') +
|
', ' + (typeof importScripts !== "undefined" ? 'Worker' : 'Main') +
|
||||||
|
@ -209,69 +195,6 @@ function getConfigString(config) {
|
||||||
')';
|
')';
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTasks(config, stepFunc) {
|
|
||||||
for (var i = 0;
|
|
||||||
i < config.numWarmUpIterations + config.numIterations; ++i) {
|
|
||||||
// Ignore the first |config.numWarmUpIterations| iterations.
|
|
||||||
if (i == config.numWarmUpIterations)
|
|
||||||
addResultClearingTask(config);
|
|
||||||
|
|
||||||
var multiplierIndex = 0;
|
|
||||||
for (var size = config.startSize;
|
|
||||||
size <= config.stopThreshold;
|
|
||||||
++multiplierIndex) {
|
|
||||||
var task = stepFunc.bind(
|
|
||||||
null,
|
|
||||||
size,
|
|
||||||
config);
|
|
||||||
tasks.push(task);
|
|
||||||
size *= config.multipliers[
|
|
||||||
multiplierIndex % config.multipliers.length];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addResultReportingTask(config, title) {
|
|
||||||
tasks.push(function(){
|
|
||||||
timerID = null;
|
|
||||||
config.addToSummary(title);
|
|
||||||
reportAverageData(config);
|
|
||||||
clearAverageData();
|
|
||||||
runNextTask(config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addResultClearingTask(config) {
|
|
||||||
tasks.push(function(){
|
|
||||||
timerID = null;
|
|
||||||
clearAverageData();
|
|
||||||
runNextTask(config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendBenchmark(config) {
|
|
||||||
config.addToLog('Send benchmark');
|
|
||||||
config.addToLog(buildLegendString(config));
|
|
||||||
|
|
||||||
tasks = [];
|
|
||||||
clearAverageData();
|
|
||||||
addTasks(config, sendBenchmarkStep);
|
|
||||||
addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
|
|
||||||
startBenchmark(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
function receiveBenchmark(config) {
|
|
||||||
config.addToLog('Receive benchmark');
|
|
||||||
config.addToLog(buildLegendString(config));
|
|
||||||
|
|
||||||
tasks = [];
|
|
||||||
clearAverageData();
|
|
||||||
addTasks(config, receiveBenchmarkStep);
|
|
||||||
addResultReportingTask(config,
|
|
||||||
'Receive Benchmark ' + getConfigString(config));
|
|
||||||
startBenchmark(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
function batchBenchmark(config) {
|
function batchBenchmark(config) {
|
||||||
config.addToLog('Batch benchmark');
|
config.addToLog('Batch benchmark');
|
||||||
config.addToLog(buildLegendString(config));
|
config.addToLog(buildLegendString(config));
|
||||||
|
@ -286,24 +209,6 @@ function batchBenchmark(config) {
|
||||||
startBenchmark(config);
|
startBenchmark(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop(config) {
|
function cleanup() {
|
||||||
clearTimeout(timerID);
|
|
||||||
timerID = null;
|
|
||||||
config.addToLog('Stopped');
|
|
||||||
destroyAllSockets();
|
destroyAllSockets();
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage = function (message) {
|
|
||||||
var config = message.data.config;
|
|
||||||
config.addToLog = workerAddToLog;
|
|
||||||
config.addToSummary = workerAddToSummary;
|
|
||||||
config.measureValue = workerMeasureValue;
|
|
||||||
if (message.data.type === 'sendBenchmark')
|
|
||||||
sendBenchmark(config);
|
|
||||||
else if (message.data.type === 'receiveBenchmark')
|
|
||||||
receiveBenchmark(config);
|
|
||||||
else if (message.data.type === 'batchBenchmark')
|
|
||||||
batchBenchmark(config);
|
|
||||||
else if (message.data.type === 'stop')
|
|
||||||
stop(config);
|
|
||||||
};
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
<!--
|
||||||
|
Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Use of this source code is governed by a BSD-style
|
||||||
|
license that can be found in the COPYING file or at
|
||||||
|
https://developers.google.com/open-source/licenses/bsd
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Fetch API benchmark</title>
|
||||||
|
<script src="util_main.js"></script>
|
||||||
|
<script src="util.js"></script>
|
||||||
|
<script src="fetch_benchmark.js"></script>
|
||||||
|
<script>
|
||||||
|
var addressBox = null;
|
||||||
|
|
||||||
|
function getConfig() {
|
||||||
|
return {
|
||||||
|
prefixUrl: addressBox.value,
|
||||||
|
printSize: getBoolFromCheckBox('printsize'),
|
||||||
|
numFetches: getIntFromInput('numFetches'),
|
||||||
|
// Initial size of messages.
|
||||||
|
numIterations: getIntFromInput('numiterations'),
|
||||||
|
numWarmUpIterations: getIntFromInput('numwarmupiterations'),
|
||||||
|
startSize: getIntFromInput('startsize'),
|
||||||
|
// Stops benchmark when the size of message exceeds this threshold.
|
||||||
|
stopThreshold: getIntFromInput('stopthreshold'),
|
||||||
|
multipliers: getFloatArrayFromInput('multipliers'),
|
||||||
|
verifyData: getBoolFromCheckBox('verifydata'),
|
||||||
|
addToLog: addToLog,
|
||||||
|
addToSummary: addToSummary,
|
||||||
|
measureValue: measureValue,
|
||||||
|
notifyAbort: notifyAbort
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSendBenchmark() {
|
||||||
|
var config = getConfig();
|
||||||
|
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReceiveBenchmark() {
|
||||||
|
var config = getConfig();
|
||||||
|
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBatchBenchmark() {
|
||||||
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStop() {
|
||||||
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
addressBox = document.getElementById('address');
|
||||||
|
logBox = document.getElementById('log');
|
||||||
|
|
||||||
|
summaryBox = document.getElementById('summary');
|
||||||
|
|
||||||
|
// Special address of pywebsocket for XHR/Fetch API benchmark.
|
||||||
|
addressBox.value = '/073be001e10950692ccbf3a2ad21c245';
|
||||||
|
|
||||||
|
addToLog(window.navigator.userAgent.toLowerCase());
|
||||||
|
addToSummary(window.navigator.userAgent.toLowerCase());
|
||||||
|
|
||||||
|
initWorker('fetch', '');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
|
||||||
|
<form id="benchmark_form">
|
||||||
|
url prefix <input type="text" id="address" size="40">
|
||||||
|
<input type="button" value="send" onclick="onSendBenchmark()">
|
||||||
|
<input type="button" value="receive" onclick="onReceiveBenchmark()">
|
||||||
|
<input type="button" value="batch" onclick="onBatchBenchmark()">
|
||||||
|
<input type="button" value="stop" onclick="onStop()">
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<input type="checkbox" id="printsize" checked>
|
||||||
|
<label for="printsize">Print size and time per message</label>
|
||||||
|
<input type="checkbox" id="verifydata" checked>
|
||||||
|
<label for="verifydata">Verify data</label>
|
||||||
|
<input type="checkbox" id="worker">
|
||||||
|
<label for="worker">Run on worker</label>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Number of fetch() requests</td>
|
||||||
|
<td><input type="text" id="numFetches" value="1"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Number of iterations</td>
|
||||||
|
<td><input type="text" id="numiterations" value="1"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Number of warm-up iterations</td>
|
||||||
|
<td><input type="text" id="numwarmupiterations" value="0"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Start size</td>
|
||||||
|
<td><input type="text" id="startsize" value="10240"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Stop threshold</td>
|
||||||
|
<td><input type="text" id="stopthreshold" value="102400000"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Multipliers</td>
|
||||||
|
<td><input type="text" id="multipliers" value="5, 2"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Set data type
|
||||||
|
<input type="radio"
|
||||||
|
name="datatyperadio"
|
||||||
|
id="datatyperadiotext"
|
||||||
|
value="text"
|
||||||
|
checked><label for="datatyperadiotext">text</label>
|
||||||
|
<input type="radio"
|
||||||
|
name="datatyperadio"
|
||||||
|
id="datatyperadioblob"
|
||||||
|
value="blob"
|
||||||
|
><label for="datatyperadioblob">blob</label>
|
||||||
|
<input type="radio"
|
||||||
|
name="datatyperadio"
|
||||||
|
id="datatyperadioarraybuffer"
|
||||||
|
value="arraybuffer"
|
||||||
|
><label for="datatyperadioarraybuffer">arraybuffer</label>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="log_div">
|
||||||
|
<textarea
|
||||||
|
id="log" rows="20" style="width: 100%" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="summary_div">
|
||||||
|
Summary
|
||||||
|
<textarea
|
||||||
|
id="summary" rows="20" style="width: 100%" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="note_div">
|
||||||
|
Note:
|
||||||
|
<ul>
|
||||||
|
<li>Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.</li>
|
||||||
|
<li>The Stddev column shows NaN when the number of iterations is set to 1.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,225 @@
|
||||||
|
// Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the COPYING file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
var isWorker = typeof importScripts !== "undefined";
|
||||||
|
|
||||||
|
if (isWorker) {
|
||||||
|
// Running on a worker
|
||||||
|
importScripts('util.js', 'util_worker.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace for holding globals.
|
||||||
|
var benchmark = {};
|
||||||
|
benchmark.startTimeInMs = 0;
|
||||||
|
|
||||||
|
var timerID = null;
|
||||||
|
|
||||||
|
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||||
|
timerID = null;
|
||||||
|
benchmark.startTimeInMs = null;
|
||||||
|
|
||||||
|
// Prepare data.
|
||||||
|
var dataArray = [];
|
||||||
|
for (var i = 0; i < config.numFetches; ++i) {
|
||||||
|
var data = null;
|
||||||
|
if (config.dataType == 'arraybuffer' ||
|
||||||
|
config.dataType == 'blob') {
|
||||||
|
data = new ArrayBuffer(size);
|
||||||
|
|
||||||
|
fillArrayBuffer(data, 0x61);
|
||||||
|
|
||||||
|
if (config.dataType == 'blob') {
|
||||||
|
data = new Blob([data]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = repeatString('a', size);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataArray.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start time measuring.
|
||||||
|
benchmark.startTimeInMs = getTimeStamp();
|
||||||
|
|
||||||
|
// Start fetch.
|
||||||
|
var promises = [];
|
||||||
|
for (var i = 0; i < config.numFetches; ++i) {
|
||||||
|
var data = dataArray[i];
|
||||||
|
var promise = fetch(config.prefixUrl + '_send',
|
||||||
|
{method: 'POST', body: data})
|
||||||
|
.then(function (response) {
|
||||||
|
if (response.status != 200) {
|
||||||
|
config.addToLog('Failed (status=' + response.status + ')');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
// Check and warn if proxy is enabled.
|
||||||
|
if (response.headers.get('Via') !== null) {
|
||||||
|
config.addToLog('WARNING: proxy seems enabled.');
|
||||||
|
}
|
||||||
|
if (config.verifyData) {
|
||||||
|
return response.text()
|
||||||
|
.then(function(text) {
|
||||||
|
if (!verifyAcknowledgement(config, text, size)) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish and report time measuring.
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(function() {
|
||||||
|
if (benchmark.startTimeInMs == null) {
|
||||||
|
config.addToLog('startTimeInMs not set');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
calculateAndLogResult(config, size, benchmark.startTimeInMs,
|
||||||
|
size * config.numFetches, isWarmUp);
|
||||||
|
runNextTask(config);
|
||||||
|
})
|
||||||
|
.catch(function(e) {
|
||||||
|
config.addToLog("ERROR: " + e);
|
||||||
|
config.notifyAbort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||||
|
timerID = null;
|
||||||
|
benchmark.startTimeInMs = null;
|
||||||
|
|
||||||
|
// Start time measuring.
|
||||||
|
benchmark.startTimeInMs = getTimeStamp();
|
||||||
|
|
||||||
|
// Start fetch.
|
||||||
|
var promises = [];
|
||||||
|
for (var i = 0; i < config.numFetches; ++i) {
|
||||||
|
var request;
|
||||||
|
if (config.methodAndCache === 'GET-NOCACHE') {
|
||||||
|
request = new Request(config.prefixUrl + '_receive_getnocache?' + size,
|
||||||
|
{method: 'GET'});
|
||||||
|
} else if (config.methodAndCache === 'GET-CACHE') {
|
||||||
|
request = new Request(config.prefixUrl + '_receive_getcache?' + size,
|
||||||
|
{method: 'GET'});
|
||||||
|
} else {
|
||||||
|
request = new Request(config.prefixUrl + '_receive',
|
||||||
|
{method: 'POST', body: size + ' none'});
|
||||||
|
}
|
||||||
|
var promise = fetch(request)
|
||||||
|
.then(function(response) {
|
||||||
|
if (response.status != 200) {
|
||||||
|
config.addToLog('Failed (status=' + this.status + ')');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
// Check and warn if proxy is enabled.
|
||||||
|
if (response.headers.get('Via') !== null) {
|
||||||
|
config.addToLog('WARNING: proxy seems enabled.');
|
||||||
|
}
|
||||||
|
if (config.dataType === 'arraybuffer') {
|
||||||
|
return response.arrayBuffer()
|
||||||
|
.then(function(arrayBuffer) {
|
||||||
|
return [arrayBuffer.byteLength,
|
||||||
|
(!config.verifyData ||
|
||||||
|
verifyArrayBuffer(arrayBuffer, 0x61))];
|
||||||
|
});
|
||||||
|
} else if (config.dataType == 'blob') {
|
||||||
|
return response.blob()
|
||||||
|
.then(function(blob) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
if (config.verifyData) {
|
||||||
|
verifyBlob(config, blob, 0x61,
|
||||||
|
function(receivedSize, verificationResult) {
|
||||||
|
resolve([receivedSize, verificationResult]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve([blob.size, true]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return response.text()
|
||||||
|
.then(function(text) {
|
||||||
|
return [text.length,
|
||||||
|
(!config.verifyData ||
|
||||||
|
text == repeatString('a', text.length))];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function(receivedSizeAndVerificationResult) {
|
||||||
|
var receivedSize = receivedSizeAndVerificationResult[0];
|
||||||
|
var verificationResult = receivedSizeAndVerificationResult[1];
|
||||||
|
if (receivedSize !== size) {
|
||||||
|
config.addToLog('Expected ' + size +
|
||||||
|
'B but received ' + receivedSize + 'B');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
if (!verificationResult) {
|
||||||
|
config.addToLog('Response verification failed');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish and report time measuring.
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(function() {
|
||||||
|
if (benchmark.startTimeInMs == null) {
|
||||||
|
config.addToLog('startTimeInMs not set');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
calculateAndLogResult(config, size, benchmark.startTimeInMs,
|
||||||
|
size * config.numFetches, isWarmUp);
|
||||||
|
runNextTask(config);
|
||||||
|
})
|
||||||
|
.catch(function(e) {
|
||||||
|
config.addToLog("ERROR: " + e);
|
||||||
|
config.notifyAbort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getConfigString(config) {
|
||||||
|
return '(' + config.dataType +
|
||||||
|
', verifyData=' + config.verifyData +
|
||||||
|
', ' + (isWorker ? 'Worker' : 'Main') +
|
||||||
|
', numFetches=' + config.numFetches +
|
||||||
|
', numIterations=' + config.numIterations +
|
||||||
|
', numWarmUpIterations=' + config.numWarmUpIterations +
|
||||||
|
')';
|
||||||
|
}
|
||||||
|
|
||||||
|
function startBenchmark(config) {
|
||||||
|
clearTimeout(timerID);
|
||||||
|
|
||||||
|
runNextTask(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function batchBenchmark(originalConfig) {
|
||||||
|
originalConfig.addToLog('Batch benchmark');
|
||||||
|
|
||||||
|
tasks = [];
|
||||||
|
clearAverageData();
|
||||||
|
|
||||||
|
var dataTypes = ['text', 'blob', 'arraybuffer'];
|
||||||
|
var stepFuncs = [sendBenchmarkStep, receiveBenchmarkStep];
|
||||||
|
var names = ['Send', 'Receive'];
|
||||||
|
for (var i = 0; i < stepFuncs.length; ++i) {
|
||||||
|
for (var j = 0; j < dataTypes.length; ++j) {
|
||||||
|
var config = cloneConfig(originalConfig);
|
||||||
|
config.dataType = dataTypes[j];
|
||||||
|
addTasks(config, stepFuncs[i]);
|
||||||
|
addResultReportingTask(config,
|
||||||
|
names[i] + ' benchmark ' + getConfigString(config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startBenchmark(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<script src="util.js"></script>
|
||||||
|
<script src="performance_test_iframe.js"></script>
|
||||||
|
<script src="fetch_benchmark.js"></script>
|
||||||
|
</head>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<script src="util.js"></script>
|
||||||
|
<script src="performance_test_iframe.js"></script>
|
||||||
|
<script src="benchmark.js"></script>
|
||||||
|
</head>
|
|
@ -0,0 +1,66 @@
|
||||||
|
function perfTestAddToLog(text) {
|
||||||
|
parent.postMessage({'command': 'log', 'value': text}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function perfTestAddToSummary(text) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function perfTestMeasureValue(value) {
|
||||||
|
parent.postMessage({'command': 'measureValue', 'value': value}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function perfTestNotifyAbort() {
|
||||||
|
parent.postMessage({'command': 'notifyAbort'}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigForPerformanceTest(connectionType, dataType, async,
|
||||||
|
verifyData, numIterations,
|
||||||
|
numWarmUpIterations) {
|
||||||
|
var prefixUrl;
|
||||||
|
if (connectionType === 'WebSocket') {
|
||||||
|
prefixUrl = 'ws://' + location.host + '/benchmark_helper';
|
||||||
|
} else {
|
||||||
|
// XHR or fetch
|
||||||
|
prefixUrl = 'http://' + location.host + '/073be001e10950692ccbf3a2ad21c245';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefixUrl: prefixUrl,
|
||||||
|
printSize: true,
|
||||||
|
numXHRs: 1,
|
||||||
|
numFetches: 1,
|
||||||
|
numSockets: 1,
|
||||||
|
// + 1 is for a warmup iteration by the Telemetry framework.
|
||||||
|
numIterations: numIterations + numWarmUpIterations + 1,
|
||||||
|
numWarmUpIterations: numWarmUpIterations,
|
||||||
|
minTotal: 10240000,
|
||||||
|
startSize: 10240000,
|
||||||
|
stopThreshold: 10240000,
|
||||||
|
multipliers: [2],
|
||||||
|
verifyData: verifyData,
|
||||||
|
dataType: dataType,
|
||||||
|
async: async,
|
||||||
|
addToLog: perfTestAddToLog,
|
||||||
|
addToSummary: perfTestAddToSummary,
|
||||||
|
measureValue: perfTestMeasureValue,
|
||||||
|
notifyAbort: perfTestNotifyAbort
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var data;
|
||||||
|
onmessage = function(message) {
|
||||||
|
var action;
|
||||||
|
if (message.data.command === 'start') {
|
||||||
|
data = message.data;
|
||||||
|
initWorker(data.connectionType, 'http://' + location.host);
|
||||||
|
action = data.benchmarkName;
|
||||||
|
} else {
|
||||||
|
action = 'stop';
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = getConfigForPerformanceTest(data.connectionType, data.dataType,
|
||||||
|
data.async, data.verifyData,
|
||||||
|
data.numIterations,
|
||||||
|
data.numWarmUpIterations);
|
||||||
|
doAction(config, data.isWorker, action);
|
||||||
|
};
|
|
@ -76,21 +76,44 @@ function calculateSpeedInKB(size, timeSpentInMs) {
|
||||||
return Math.round(size / timeSpentInMs * 1000) / 1000;
|
return Math.round(size / timeSpentInMs * 1000) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateAndLogResult(config, size, startTimeInMs, totalSize) {
|
function calculateAndLogResult(config, size, startTimeInMs, totalSize,
|
||||||
|
isWarmUp) {
|
||||||
var timeSpentInMs = getTimeStamp() - startTimeInMs;
|
var timeSpentInMs = getTimeStamp() - startTimeInMs;
|
||||||
var speed = calculateSpeedInKB(totalSize, timeSpentInMs);
|
var speed = calculateSpeedInKB(totalSize, timeSpentInMs);
|
||||||
var timePerMessageInMs = timeSpentInMs / (totalSize / size);
|
var timePerMessageInMs = timeSpentInMs / (totalSize / size);
|
||||||
if (!results[size]) {
|
if (!isWarmUp) {
|
||||||
results[size] = {n: 0, sum_t: 0, sum_t2: 0};
|
config.measureValue(timePerMessageInMs);
|
||||||
|
if (!results[size]) {
|
||||||
|
results[size] = {n: 0, sum_t: 0, sum_t2: 0};
|
||||||
|
}
|
||||||
|
results[size].n ++;
|
||||||
|
results[size].sum_t += timePerMessageInMs;
|
||||||
|
results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs;
|
||||||
}
|
}
|
||||||
config.measureValue(timePerMessageInMs);
|
|
||||||
results[size].n ++;
|
|
||||||
results[size].sum_t += timePerMessageInMs;
|
|
||||||
results[size].sum_t2 += timePerMessageInMs * timePerMessageInMs;
|
|
||||||
config.addToLog(formatResultInKiB(size, timePerMessageInMs, -1, speed,
|
config.addToLog(formatResultInKiB(size, timePerMessageInMs, -1, speed,
|
||||||
config.printSize));
|
config.printSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function repeatString(str, count) {
|
||||||
|
var data = '';
|
||||||
|
var expChunk = str;
|
||||||
|
var remain = count;
|
||||||
|
while (true) {
|
||||||
|
if (remain % 2) {
|
||||||
|
data += expChunk;
|
||||||
|
remain = (remain - 1) / 2;
|
||||||
|
} else {
|
||||||
|
remain /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remain == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
expChunk = expChunk + expChunk;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
function fillArrayBuffer(buffer, c) {
|
function fillArrayBuffer(buffer, c) {
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
|
@ -175,3 +198,130 @@ function cloneConfig(obj) {
|
||||||
}
|
}
|
||||||
return newObj;
|
return newObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tasks = [];
|
||||||
|
|
||||||
|
function runNextTask(config) {
|
||||||
|
var task = tasks.shift();
|
||||||
|
if (task == undefined) {
|
||||||
|
config.addToLog('Finished');
|
||||||
|
cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timerID = setTimeout(task, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLegendString(config) {
|
||||||
|
var legend = ''
|
||||||
|
if (config.printSize)
|
||||||
|
legend = 'Message size in KiB, Time/message in ms, ';
|
||||||
|
legend += 'Speed in kB/s';
|
||||||
|
return legend;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTasks(config, stepFunc) {
|
||||||
|
for (var i = 0;
|
||||||
|
i < config.numWarmUpIterations + config.numIterations; ++i) {
|
||||||
|
var multiplierIndex = 0;
|
||||||
|
for (var size = config.startSize;
|
||||||
|
size <= config.stopThreshold;
|
||||||
|
++multiplierIndex) {
|
||||||
|
var task = stepFunc.bind(
|
||||||
|
null,
|
||||||
|
size,
|
||||||
|
config,
|
||||||
|
i < config.numWarmUpIterations);
|
||||||
|
tasks.push(task);
|
||||||
|
var multiplier = config.multipliers[
|
||||||
|
multiplierIndex % config.multipliers.length];
|
||||||
|
if (multiplier <= 1) {
|
||||||
|
config.addToLog('Invalid multiplier ' + multiplier);
|
||||||
|
config.notifyAbort();
|
||||||
|
throw new Error('Invalid multipler');
|
||||||
|
}
|
||||||
|
size = Math.ceil(size * multiplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addResultReportingTask(config, title) {
|
||||||
|
tasks.push(function(){
|
||||||
|
timerID = null;
|
||||||
|
config.addToSummary(title);
|
||||||
|
reportAverageData(config);
|
||||||
|
clearAverageData();
|
||||||
|
runNextTask(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendBenchmark(config) {
|
||||||
|
config.addToLog('Send benchmark');
|
||||||
|
config.addToLog(buildLegendString(config));
|
||||||
|
|
||||||
|
tasks = [];
|
||||||
|
clearAverageData();
|
||||||
|
addTasks(config, sendBenchmarkStep);
|
||||||
|
addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
|
||||||
|
startBenchmark(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveBenchmark(config) {
|
||||||
|
config.addToLog('Receive benchmark');
|
||||||
|
config.addToLog(buildLegendString(config));
|
||||||
|
|
||||||
|
tasks = [];
|
||||||
|
clearAverageData();
|
||||||
|
addTasks(config, receiveBenchmarkStep);
|
||||||
|
addResultReportingTask(config,
|
||||||
|
'Receive Benchmark ' + getConfigString(config));
|
||||||
|
startBenchmark(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop(config) {
|
||||||
|
clearTimeout(timerID);
|
||||||
|
timerID = null;
|
||||||
|
tasks = [];
|
||||||
|
config.addToLog('Stopped');
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
var worker;
|
||||||
|
|
||||||
|
function initWorker(connectionType, origin) {
|
||||||
|
var scriptPath =
|
||||||
|
connectionType === 'WebSocket' ? '/benchmark.js' :
|
||||||
|
connectionType === 'XHR' ? '/xhr_benchmark.js' :
|
||||||
|
'/fetch_benchmark.js'; // connectionType === 'fetch'
|
||||||
|
worker = new Worker(origin + scriptPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doAction(config, isWindowToWorker, action) {
|
||||||
|
if (isWindowToWorker) {
|
||||||
|
worker.onmessage = function(addToLog, addToSummary,
|
||||||
|
measureValue, notifyAbort, message) {
|
||||||
|
if (message.data.type === 'addToLog')
|
||||||
|
addToLog(message.data.data);
|
||||||
|
else if (message.data.type === 'addToSummary')
|
||||||
|
addToSummary(message.data.data);
|
||||||
|
else if (message.data.type === 'measureValue')
|
||||||
|
measureValue(message.data.data);
|
||||||
|
else if (message.data.type === 'notifyAbort')
|
||||||
|
notifyAbort();
|
||||||
|
}.bind(undefined, config.addToLog, config.addToSummary,
|
||||||
|
config.measureValue, config.notifyAbort);
|
||||||
|
config.addToLog = undefined;
|
||||||
|
config.addToSummary = undefined;
|
||||||
|
config.measureValue = undefined;
|
||||||
|
config.notifyAbort = undefined;
|
||||||
|
worker.postMessage({type: action, config: config});
|
||||||
|
} else {
|
||||||
|
if (action === 'sendBenchmark')
|
||||||
|
sendBenchmark(config);
|
||||||
|
else if (action === 'receiveBenchmark')
|
||||||
|
receiveBenchmark(config);
|
||||||
|
else if (action === 'batchBenchmark')
|
||||||
|
batchBenchmark(config);
|
||||||
|
else if (action === 'stop')
|
||||||
|
stop(config);
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,12 @@ function addToSummary(log) {
|
||||||
function measureValue(value) {
|
function measureValue(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// config.notifyAbort is called when the benchmark failed and aborted, and
|
||||||
|
// intended to be used in Performance Tests.
|
||||||
|
// Do nothing here in non-PerformanceTest.
|
||||||
|
function notifyAbort() {
|
||||||
|
}
|
||||||
|
|
||||||
function getIntFromInput(id) {
|
function getIntFromInput(id) {
|
||||||
return parseInt(document.getElementById(id).value);
|
return parseInt(document.getElementById(id).value);
|
||||||
}
|
}
|
||||||
|
@ -53,11 +59,7 @@ function getIntArrayFromInput(id) {
|
||||||
return strArray.map(function(str) { return parseInt(str, 10); });
|
return strArray.map(function(str) { return parseInt(str, 10); });
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMessage(message) {
|
function getFloatArrayFromInput(id) {
|
||||||
if (message.data.type === 'addToLog')
|
var strArray = document.getElementById(id).value.split(',');
|
||||||
addToLog(message.data.data);
|
return strArray.map(parseFloat);
|
||||||
else if (message.data.type === 'addToSummary')
|
|
||||||
addToSummary(message.data.data);
|
|
||||||
else if (message.data.type === 'measureValue')
|
|
||||||
measureValue(message.data.data);
|
|
||||||
}
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the COPYING file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
// Utilities for example applications (for the worker threads only).
|
||||||
|
|
||||||
|
onmessage = function (message) {
|
||||||
|
var config = message.data.config;
|
||||||
|
config.addToLog = function(text) {
|
||||||
|
postMessage({type: 'addToLog', data: text}); };
|
||||||
|
config.addToSummary = function(text) {
|
||||||
|
postMessage({type: 'addToSummary', data: text}); };
|
||||||
|
config.measureValue = function(value) {
|
||||||
|
postMessage({type: 'measureValue', data: value}); };
|
||||||
|
config.notifyAbort = function() { postMessage({type: 'notifyAbort'}); };
|
||||||
|
|
||||||
|
doAction(config, false, message.data.type);
|
||||||
|
};
|
|
@ -50,70 +50,36 @@ function getConfig() {
|
||||||
startSize: getIntFromInput('startsize'),
|
startSize: getIntFromInput('startsize'),
|
||||||
// Stops benchmark when the size of message exceeds this threshold.
|
// Stops benchmark when the size of message exceeds this threshold.
|
||||||
stopThreshold: getIntFromInput('stopthreshold'),
|
stopThreshold: getIntFromInput('stopthreshold'),
|
||||||
// If the size of each message is small, send/receive multiple messages
|
multipliers: getFloatArrayFromInput('multipliers'),
|
||||||
// until the sum of sizes reaches this threshold.
|
verifyData: getBoolFromCheckBox('verifydata'),
|
||||||
// minTotal: getIntFromInput('mintotal'),
|
methodAndCache: getStringFromRadioBox('methodandcache'),
|
||||||
// minTotal is not yet implemented on XHR benchmark
|
addToLog: addToLog,
|
||||||
multipliers: getIntArrayFromInput('multipliers'),
|
addToSummary: addToSummary,
|
||||||
verifyData: getBoolFromCheckBox('verifydata')
|
measureValue: measureValue,
|
||||||
|
notifyAbort: notifyAbort
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var worker = new Worker('xhr_benchmark.js');
|
|
||||||
worker.onmessage = onMessage;
|
|
||||||
|
|
||||||
function onSendBenchmark() {
|
function onSendBenchmark() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'sendBenchmark', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
sendBenchmark(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReceiveBenchmark() {
|
function onReceiveBenchmark() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'receiveBenchmark', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
receiveBenchmark(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBatchBenchmark() {
|
function onBatchBenchmark() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'batchBenchmark', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
batchBenchmark(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStop() {
|
function onStop() {
|
||||||
var config = getConfig();
|
var config = getConfig();
|
||||||
|
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||||
if (getBoolFromCheckBox('worker')) {
|
|
||||||
worker.postMessage({type: 'stop', config: config});
|
|
||||||
} else {
|
|
||||||
config.addToLog = addToLog;
|
|
||||||
config.addToSummary = addToSummary;
|
|
||||||
config.measureValue = measureValue;
|
|
||||||
stop(config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
@ -127,6 +93,8 @@ function init() {
|
||||||
|
|
||||||
addToLog(window.navigator.userAgent.toLowerCase());
|
addToLog(window.navigator.userAgent.toLowerCase());
|
||||||
addToSummary(window.navigator.userAgent.toLowerCase());
|
addToSummary(window.navigator.userAgent.toLowerCase());
|
||||||
|
|
||||||
|
initWorker('XHR', '');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -178,10 +146,6 @@ function init() {
|
||||||
<td>Stop threshold</td>
|
<td>Stop threshold</td>
|
||||||
<td><input type="text" id="stopthreshold" value="102400000"></td>
|
<td><input type="text" id="stopthreshold" value="102400000"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Minimum total</td>
|
|
||||||
<td><input type="text" id="mintotal" value="102400000"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Multipliers</td>
|
<td>Multipliers</td>
|
||||||
<td><input type="text" id="multipliers" value="5, 2"></td>
|
<td><input type="text" id="multipliers" value="5, 2"></td>
|
||||||
|
@ -204,6 +168,26 @@ function init() {
|
||||||
id="datatyperadioarraybuffer"
|
id="datatyperadioarraybuffer"
|
||||||
value="arraybuffer"
|
value="arraybuffer"
|
||||||
><label for="datatyperadioarraybuffer">arraybuffer</label>
|
><label for="datatyperadioarraybuffer">arraybuffer</label>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
Set HTTP method and cache control
|
||||||
|
<input type="radio"
|
||||||
|
name="methodandcache"
|
||||||
|
id="methodandcachePOST"
|
||||||
|
value="POST"
|
||||||
|
checked><label for="methodandcachePOST">POST (No Cache)</label>
|
||||||
|
<input type="radio"
|
||||||
|
name="methodandcache"
|
||||||
|
id="methodandcacheGETNOCACHE"
|
||||||
|
value="GET-NOCACHE"
|
||||||
|
><label for="methodandcacheGETNOCACHE">GET (No Cache)</label>
|
||||||
|
<input type="radio"
|
||||||
|
name="methodandcache"
|
||||||
|
id="methodandcacheGETCACHE"
|
||||||
|
value="GET-CACHE"
|
||||||
|
><label for="methodandcacheGETCACHE">GET (Cache)</label>
|
||||||
|
<br>
|
||||||
|
<span style="font-size: 80%">(Cache control: receive only. Cache is valid for 10 seconds. This config controls Cache-control HTTP response header. Browsers might not cache data e.g. when it is large)</span>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="log_div">
|
<div id="log_div">
|
||||||
|
@ -216,7 +200,13 @@ function init() {
|
||||||
id="summary" rows="20" style="width: 100%" readonly></textarea>
|
id="summary" rows="20" style="width: 100%" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Note: Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.
|
<div id="note_div">
|
||||||
|
Note:
|
||||||
|
<ul>
|
||||||
|
<li>Effect of RTT and time spent for ArrayBuffer creation in receive benchmarks are not eliminated.</li>
|
||||||
|
<li>The Stddev column shows NaN when the number of iterations is set to 1.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -33,27 +33,7 @@ function destroyAllXHRs() {
|
||||||
// gc() might be needed for Chrome/Blob
|
// gc() might be needed for Chrome/Blob
|
||||||
}
|
}
|
||||||
|
|
||||||
function repeatString(str, count) {
|
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||||
var data = '';
|
|
||||||
var expChunk = str;
|
|
||||||
var remain = count;
|
|
||||||
while (true) {
|
|
||||||
if (remain % 2) {
|
|
||||||
data += expChunk;
|
|
||||||
remain = (remain - 1) / 2;
|
|
||||||
} else {
|
|
||||||
remain /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remain == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
expChunk = expChunk + expChunk;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendBenchmarkStep(size, config) {
|
|
||||||
timerID = null;
|
timerID = null;
|
||||||
|
|
||||||
benchmark.startTimeInMs = null;
|
benchmark.startTimeInMs = null;
|
||||||
|
@ -68,12 +48,14 @@ function sendBenchmarkStep(size, config) {
|
||||||
if (this.status != 200) {
|
if (this.status != 200) {
|
||||||
config.addToLog('Failed (status=' + this.status + ')');
|
config.addToLog('Failed (status=' + this.status + ')');
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.verifyData &&
|
if (config.verifyData &&
|
||||||
!verifyAcknowledgement(config, this.response, size)) {
|
!verifyAcknowledgement(config, this.response, size)) {
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,10 +68,17 @@ function sendBenchmarkStep(size, config) {
|
||||||
if (benchmark.startTimeInMs == null) {
|
if (benchmark.startTimeInMs == null) {
|
||||||
config.addToLog('startTimeInMs not set');
|
config.addToLog('startTimeInMs not set');
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
// Check and warn if proxy is enabled.
|
||||||
|
if (this.getResponseHeader('Via') !== null) {
|
||||||
|
config.addToLog('WARNING: proxy seems enabled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||||
|
isWarmUp);
|
||||||
|
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
|
||||||
|
@ -134,7 +123,7 @@ function sendBenchmarkStep(size, config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiveBenchmarkStep(size, config) {
|
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||||
timerID = null;
|
timerID = null;
|
||||||
|
|
||||||
benchmark.startTimeInMs = null;
|
benchmark.startTimeInMs = null;
|
||||||
|
@ -145,6 +134,7 @@ function receiveBenchmarkStep(size, config) {
|
||||||
if (!verificationResult) {
|
if (!verificationResult) {
|
||||||
config.addToLog('Response verification failed');
|
config.addToLog('Response verification failed');
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,10 +147,12 @@ function receiveBenchmarkStep(size, config) {
|
||||||
if (benchmark.startTimeInMs == null) {
|
if (benchmark.startTimeInMs == null) {
|
||||||
config.addToLog('startTimeInMs not set');
|
config.addToLog('startTimeInMs not set');
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||||
|
isWarmUp);
|
||||||
|
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
|
||||||
|
@ -175,9 +167,15 @@ function receiveBenchmarkStep(size, config) {
|
||||||
if (this.status != 200) {
|
if (this.status != 200) {
|
||||||
config.addToLog('Failed (status=' + this.status + ')');
|
config.addToLog('Failed (status=' + this.status + ')');
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check and warn if proxy is enabled.
|
||||||
|
if (this.getResponseHeader('Via') !== null) {
|
||||||
|
config.addToLog('WARNING: proxy seems enabled.');
|
||||||
|
}
|
||||||
|
|
||||||
var bytesReceived = -1;
|
var bytesReceived = -1;
|
||||||
if (this.responseType == 'arraybuffer') {
|
if (this.responseType == 'arraybuffer') {
|
||||||
bytesReceived = this.response.byteLength;
|
bytesReceived = this.response.byteLength;
|
||||||
|
@ -190,6 +188,7 @@ function receiveBenchmarkStep(size, config) {
|
||||||
config.addToLog('Expected ' + size +
|
config.addToLog('Expected ' + size +
|
||||||
'B but received ' + bytesReceived + 'B');
|
'B but received ' + bytesReceived + 'B');
|
||||||
destroyAllXHRs();
|
destroyAllXHRs();
|
||||||
|
config.notifyAbort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,9 +219,21 @@ function receiveBenchmarkStep(size, config) {
|
||||||
|
|
||||||
for (var i = 0; i < xhrs.length; ++i) {
|
for (var i = 0; i < xhrs.length; ++i) {
|
||||||
var xhr = xhrs[i];
|
var xhr = xhrs[i];
|
||||||
xhr.open('POST', config.prefixUrl + '_receive', config.async);
|
if (config.methodAndCache === 'GET-NOCACHE') {
|
||||||
xhr.responseType = config.dataType;
|
xhr.open('GET', config.prefixUrl + '_receive_getnocache?' + size,
|
||||||
xhr.send(size + ' none');
|
config.async);
|
||||||
|
xhr.responseType = config.dataType;
|
||||||
|
xhr.send();
|
||||||
|
} else if (config.methodAndCache === 'GET-CACHE') {
|
||||||
|
xhr.open('GET', config.prefixUrl + '_receive_getcache?' + size,
|
||||||
|
config.async);
|
||||||
|
xhr.responseType = config.dataType;
|
||||||
|
xhr.send();
|
||||||
|
} else {
|
||||||
|
xhr.open('POST', config.prefixUrl + '_receive', config.async);
|
||||||
|
xhr.responseType = config.dataType;
|
||||||
|
xhr.send(size + ' none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,94 +256,6 @@ function startBenchmark(config) {
|
||||||
runNextTask(config);
|
runNextTask(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(hiroshige): the following code is the same as benchmark.html
|
|
||||||
// and some of them should be merged into e.g. util.js
|
|
||||||
|
|
||||||
var tasks = [];
|
|
||||||
|
|
||||||
function runNextTask(config) {
|
|
||||||
var task = tasks.shift();
|
|
||||||
if (task == undefined) {
|
|
||||||
config.addToLog('Finished');
|
|
||||||
destroyAllXHRs();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timerID = setTimeout(task, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLegendString(config) {
|
|
||||||
var legend = ''
|
|
||||||
if (config.printSize)
|
|
||||||
legend = 'Message size in KiB, Time/message in ms, ';
|
|
||||||
legend += 'Speed in kB/s';
|
|
||||||
return legend;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTasks(config, stepFunc) {
|
|
||||||
for (var i = 0;
|
|
||||||
i < config.numWarmUpIterations + config.numIterations; ++i) {
|
|
||||||
// Ignore the first |config.numWarmUpIterations| iterations.
|
|
||||||
if (i == config.numWarmUpIterations)
|
|
||||||
addResultClearingTask(config);
|
|
||||||
|
|
||||||
var multiplierIndex = 0;
|
|
||||||
for (var size = config.startSize;
|
|
||||||
size <= config.stopThreshold;
|
|
||||||
++multiplierIndex) {
|
|
||||||
var task = stepFunc.bind(
|
|
||||||
null,
|
|
||||||
size,
|
|
||||||
config);
|
|
||||||
tasks.push(task);
|
|
||||||
size *= config.multipliers[
|
|
||||||
multiplierIndex % config.multipliers.length];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addResultReportingTask(config, title) {
|
|
||||||
tasks.push(function(){
|
|
||||||
timerID = null;
|
|
||||||
config.addToSummary(title);
|
|
||||||
reportAverageData(config);
|
|
||||||
clearAverageData();
|
|
||||||
runNextTask(config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addResultClearingTask(config) {
|
|
||||||
tasks.push(function(){
|
|
||||||
timerID = null;
|
|
||||||
clearAverageData();
|
|
||||||
runNextTask(config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------
|
|
||||||
|
|
||||||
function sendBenchmark(config) {
|
|
||||||
config.addToLog('Send benchmark');
|
|
||||||
config.addToLog(buildLegendString(config));
|
|
||||||
|
|
||||||
tasks = [];
|
|
||||||
clearAverageData();
|
|
||||||
addTasks(config, sendBenchmarkStep);
|
|
||||||
addResultReportingTask(config, 'Send Benchmark ' + getConfigString(config));
|
|
||||||
startBenchmark(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
function receiveBenchmark(config) {
|
|
||||||
config.addToLog('Receive benchmark');
|
|
||||||
config.addToLog(buildLegendString(config));
|
|
||||||
|
|
||||||
tasks = [];
|
|
||||||
clearAverageData();
|
|
||||||
addTasks(config, receiveBenchmarkStep);
|
|
||||||
addResultReportingTask(config,
|
|
||||||
'Receive Benchmark ' + getConfigString(config));
|
|
||||||
startBenchmark(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
function batchBenchmark(originalConfig) {
|
function batchBenchmark(originalConfig) {
|
||||||
originalConfig.addToLog('Batch benchmark');
|
originalConfig.addToLog('Batch benchmark');
|
||||||
|
|
||||||
|
@ -365,25 +288,5 @@ function batchBenchmark(originalConfig) {
|
||||||
startBenchmark(config);
|
startBenchmark(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
function stop(config) {
|
|
||||||
destroyAllXHRs();
|
|
||||||
clearTimeout(timerID);
|
|
||||||
timerID = null;
|
|
||||||
config.addToLog('Stopped');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage = function (message) {
|
|
||||||
var config = message.data.config;
|
|
||||||
config.addToLog = workerAddToLog;
|
|
||||||
config.addToSummary = workerAddToSummary;
|
|
||||||
config.measureValue = workerMeasureValue;
|
|
||||||
if (message.data.type === 'sendBenchmark')
|
|
||||||
sendBenchmark(config);
|
|
||||||
else if (message.data.type === 'receiveBenchmark')
|
|
||||||
receiveBenchmark(config);
|
|
||||||
else if (message.data.type === 'batchBenchmark')
|
|
||||||
batchBenchmark(config);
|
|
||||||
else if (message.data.type === 'stop')
|
|
||||||
stop(config);
|
|
||||||
};
|
|
|
@ -12,15 +12,19 @@ https://developers.google.com/open-source/licenses/bsd
|
||||||
<script src="util_main.js"></script>
|
<script src="util_main.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var events = [];
|
var events = [];
|
||||||
|
var startTime = 0;
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
events = [];
|
events = [];
|
||||||
|
startTime = Date.now();
|
||||||
|
|
||||||
function pushToLog(type) {
|
function pushToLog(type) {
|
||||||
|
var time = Date.now();
|
||||||
if (events.length != 0 && type === events[events.length - 1].type) {
|
if (events.length != 0 && type === events[events.length - 1].type) {
|
||||||
events[events.length - 1].count += 1;
|
events[events.length - 1].count += 1;
|
||||||
|
events[events.length - 1].last = time;
|
||||||
} else {
|
} else {
|
||||||
events.push({type: type, count: 1});
|
events.push({type: type, count: 1, first: time, last: time});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,14 +89,23 @@ function print() {
|
||||||
var result = '';
|
var result = '';
|
||||||
for (var i = 0; i < events.length; ++i) {
|
for (var i = 0; i < events.length; ++i) {
|
||||||
var event = events[i];
|
var event = events[i];
|
||||||
result += event.type + ' * ' + event.count + '\n';
|
var line = '';
|
||||||
|
line += (event.first - startTime) + "ms";
|
||||||
|
if (event.count > 1)
|
||||||
|
line += "-" + (event.last - startTime) + "ms";
|
||||||
|
else
|
||||||
|
line += " ";
|
||||||
|
while(line.length < 15)
|
||||||
|
line += " ";
|
||||||
|
line += ": " + event.type + ' * ' + event.count + '\n';
|
||||||
|
result += line;
|
||||||
}
|
}
|
||||||
document.getElementById('log').value = result;
|
document.getElementById('log').value = result;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<textarea id="log" rows="10" cols="40" readonly></textarea>
|
<textarea id="log" rows="10" cols="70" readonly></textarea>
|
||||||
<br/>
|
<br/>
|
||||||
Size: <input type="text" id="size" value="65536"><br/>
|
Size: <input type="text" id="size" value="65536"><br/>
|
||||||
<input type="checkbox" id="chunkedresponse">
|
<input type="checkbox" id="chunkedresponse">
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<script src="util.js"></script>
|
||||||
|
<script src="performance_test_iframe.js"></script>
|
||||||
|
<script src="xhr_benchmark.js"></script>
|
||||||
|
</head>
|
|
@ -807,7 +807,8 @@ class Stream(StreamBase):
|
||||||
|
|
||||||
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='',
|
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='',
|
||||||
wait_response=True):
|
wait_response=True):
|
||||||
"""Closes a WebSocket connection.
|
"""Closes a WebSocket connection. Note that this method blocks until
|
||||||
|
it receives acknowledgement to the closing handshake.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code: Status code for close frame. If code is None, a close
|
code: Status code for close frame. If code is None, a close
|
||||||
|
@ -824,6 +825,12 @@ class Stream(StreamBase):
|
||||||
'Requested close_connection but server is already terminated')
|
'Requested close_connection but server is already terminated')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# When we receive a close frame, we call _process_close_message().
|
||||||
|
# _process_close_message() immediately acknowledges to the
|
||||||
|
# server-initiated closing handshake and sets server_terminated to
|
||||||
|
# True. So, here we can assume that we haven't received any close
|
||||||
|
# frame. We're initiating a closing handshake.
|
||||||
|
|
||||||
if code is None:
|
if code is None:
|
||||||
if reason is not None and len(reason) > 0:
|
if reason is not None and len(reason) > 0:
|
||||||
raise BadOperationException(
|
raise BadOperationException(
|
|
@ -102,10 +102,8 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
|
||||||
|
|
||||||
# Extensions
|
# Extensions
|
||||||
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
|
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
|
||||||
PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
|
|
||||||
PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
|
PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
|
||||||
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
|
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
|
||||||
X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress'
|
|
||||||
MUX_EXTENSION = 'mux_DO_NOT_USE'
|
MUX_EXTENSION = 'mux_DO_NOT_USE'
|
||||||
|
|
||||||
# Status codes
|
# Status codes
|
||||||
|
@ -153,9 +151,8 @@ def is_control_opcode(opcode):
|
||||||
|
|
||||||
|
|
||||||
class ExtensionParameter(object):
|
class ExtensionParameter(object):
|
||||||
"""Holds information about an extension which is exchanged on extension
|
|
||||||
negotiation in opening handshake.
|
"""This is exchanged on extension negotiation in opening handshake."""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self._name = name
|
self._name = name
|
||||||
|
@ -166,30 +163,39 @@ class ExtensionParameter(object):
|
||||||
self._parameters = []
|
self._parameters = []
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
|
"""Return the extension name."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
def add_parameter(self, name, value):
|
def add_parameter(self, name, value):
|
||||||
|
"""Add a parameter."""
|
||||||
self._parameters.append((name, value))
|
self._parameters.append((name, value))
|
||||||
|
|
||||||
def get_parameters(self):
|
def get_parameters(self):
|
||||||
|
"""Return the parameters."""
|
||||||
return self._parameters
|
return self._parameters
|
||||||
|
|
||||||
def get_parameter_names(self):
|
def get_parameter_names(self):
|
||||||
|
"""Return the names of the parameters."""
|
||||||
return [name for name, unused_value in self._parameters]
|
return [name for name, unused_value in self._parameters]
|
||||||
|
|
||||||
def has_parameter(self, name):
|
def has_parameter(self, name):
|
||||||
|
"""Test if a parameter exists."""
|
||||||
for param_name, param_value in self._parameters:
|
for param_name, param_value in self._parameters:
|
||||||
if param_name == name:
|
if param_name == name:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_parameter_value(self, name):
|
def get_parameter_value(self, name):
|
||||||
|
"""Get the value of a specific parameter."""
|
||||||
for param_name, param_value in self._parameters:
|
for param_name, param_value in self._parameters:
|
||||||
if param_name == name:
|
if param_name == name:
|
||||||
return param_value
|
return param_value
|
||||||
|
|
||||||
|
|
||||||
class ExtensionParsingException(Exception):
|
class ExtensionParsingException(Exception):
|
||||||
|
|
||||||
|
"""Exception to handle errors in extension parsing."""
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
super(ExtensionParsingException, self).__init__(name)
|
super(ExtensionParsingException, self).__init__(name)
|
||||||
|
|
||||||
|
@ -244,12 +250,11 @@ def _parse_extension(state):
|
||||||
|
|
||||||
|
|
||||||
def parse_extensions(data):
|
def parse_extensions(data):
|
||||||
"""Parses Sec-WebSocket-Extensions header value returns a list of
|
"""Parse Sec-WebSocket-Extensions header value.
|
||||||
ExtensionParameter objects.
|
|
||||||
|
|
||||||
|
Returns a list of ExtensionParameter objects.
|
||||||
Leading LWSes must be trimmed.
|
Leading LWSes must be trimmed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
state = http_header_util.ParsingState(data)
|
state = http_header_util.ParsingState(data)
|
||||||
|
|
||||||
extension_list = []
|
extension_list = []
|
||||||
|
@ -279,8 +284,7 @@ def parse_extensions(data):
|
||||||
|
|
||||||
|
|
||||||
def format_extension(extension):
|
def format_extension(extension):
|
||||||
"""Formats an ExtensionParameter object."""
|
"""Format an ExtensionParameter object."""
|
||||||
|
|
||||||
formatted_params = [extension.name()]
|
formatted_params = [extension.name()]
|
||||||
for param_name, param_value in extension.get_parameters():
|
for param_name, param_value in extension.get_parameters():
|
||||||
if param_value is None:
|
if param_value is None:
|
||||||
|
@ -292,8 +296,7 @@ def format_extension(extension):
|
||||||
|
|
||||||
|
|
||||||
def format_extensions(extension_list):
|
def format_extensions(extension_list):
|
||||||
"""Formats a list of ExtensionParameter objects."""
|
"""Format a list of ExtensionParameter objects."""
|
||||||
|
|
||||||
formatted_extension_list = []
|
formatted_extension_list = []
|
||||||
for extension in extension_list:
|
for extension in extension_list:
|
||||||
formatted_extension_list.append(format_extension(extension))
|
formatted_extension_list.append(format_extension(extension))
|
|
@ -327,103 +327,8 @@ _available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
|
||||||
_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
|
_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
|
||||||
|
|
||||||
|
|
||||||
def _parse_compression_method(data):
|
|
||||||
"""Parses the value of "method" extension parameter."""
|
|
||||||
|
|
||||||
return common.parse_extensions(data)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_accepted_method_desc(method_name, method_params):
|
|
||||||
"""Creates accepted-method-desc from given method name and parameters"""
|
|
||||||
|
|
||||||
extension = common.ExtensionParameter(method_name)
|
|
||||||
for name, value in method_params:
|
|
||||||
extension.add_parameter(name, value)
|
|
||||||
return common.format_extension(extension)
|
|
||||||
|
|
||||||
|
|
||||||
class CompressionExtensionProcessorBase(ExtensionProcessorInterface):
|
|
||||||
"""Base class for perframe-compress and permessage-compress extension."""
|
|
||||||
|
|
||||||
_METHOD_PARAM = 'method'
|
|
||||||
|
|
||||||
def __init__(self, request):
|
|
||||||
ExtensionProcessorInterface.__init__(self, request)
|
|
||||||
self._logger = util.get_class_logger(self)
|
|
||||||
self._compression_method_name = None
|
|
||||||
self._compression_processor = None
|
|
||||||
self._compression_processor_hook = None
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def _lookup_compression_processor(self, method_desc):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _get_compression_processor_response(self):
|
|
||||||
"""Looks up the compression processor based on the self._request and
|
|
||||||
returns the compression processor's response.
|
|
||||||
"""
|
|
||||||
|
|
||||||
method_list = self._request.get_parameter_value(self._METHOD_PARAM)
|
|
||||||
if method_list is None:
|
|
||||||
return None
|
|
||||||
methods = _parse_compression_method(method_list)
|
|
||||||
if methods is None:
|
|
||||||
return None
|
|
||||||
comression_processor = None
|
|
||||||
# The current implementation tries only the first method that matches
|
|
||||||
# supported algorithm. Following methods aren't tried even if the
|
|
||||||
# first one is rejected.
|
|
||||||
# TODO(bashi): Need to clarify this behavior.
|
|
||||||
for method_desc in methods:
|
|
||||||
compression_processor = self._lookup_compression_processor(
|
|
||||||
method_desc)
|
|
||||||
if compression_processor is not None:
|
|
||||||
self._compression_method_name = method_desc.name()
|
|
||||||
break
|
|
||||||
if compression_processor is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self._compression_processor_hook:
|
|
||||||
self._compression_processor_hook(compression_processor)
|
|
||||||
|
|
||||||
processor_response = compression_processor.get_extension_response()
|
|
||||||
if processor_response is None:
|
|
||||||
return None
|
|
||||||
self._compression_processor = compression_processor
|
|
||||||
return processor_response
|
|
||||||
|
|
||||||
def _get_extension_response_internal(self):
|
|
||||||
processor_response = self._get_compression_processor_response()
|
|
||||||
if processor_response is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
response = common.ExtensionParameter(self._request.name())
|
|
||||||
accepted_method_desc = _create_accepted_method_desc(
|
|
||||||
self._compression_method_name,
|
|
||||||
processor_response.get_parameters())
|
|
||||||
response.add_parameter(self._METHOD_PARAM, accepted_method_desc)
|
|
||||||
self._logger.debug(
|
|
||||||
'Enable %s extension (method: %s)' %
|
|
||||||
(self._request.name(), self._compression_method_name))
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _setup_stream_options_internal(self, stream_options):
|
|
||||||
if self._compression_processor is None:
|
|
||||||
return
|
|
||||||
self._compression_processor.setup_stream_options(stream_options)
|
|
||||||
|
|
||||||
def set_compression_processor_hook(self, hook):
|
|
||||||
self._compression_processor_hook = hook
|
|
||||||
|
|
||||||
def get_compression_processor(self):
|
|
||||||
return self._compression_processor
|
|
||||||
|
|
||||||
|
|
||||||
class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
||||||
"""permessage-deflate extension processor. It's also used for
|
"""permessage-deflate extension processor.
|
||||||
permessage-compress extension when the deflate method is chosen.
|
|
||||||
|
|
||||||
Specification:
|
Specification:
|
||||||
http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
|
http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
|
||||||
|
@ -434,15 +339,8 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
||||||
_CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
|
_CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
|
||||||
_CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
|
_CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
|
||||||
|
|
||||||
def __init__(self, request, draft08=True):
|
def __init__(self, request):
|
||||||
"""Construct PerMessageDeflateExtensionProcessor
|
"""Construct PerMessageDeflateExtensionProcessor."""
|
||||||
|
|
||||||
Args:
|
|
||||||
draft08: Follow the constraints on the parameters that were not
|
|
||||||
specified for permessage-compress but are specified for
|
|
||||||
permessage-deflate as on
|
|
||||||
draft-ietf-hybi-permessage-compression-08.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ExtensionProcessorInterface.__init__(self, request)
|
ExtensionProcessorInterface.__init__(self, request)
|
||||||
self._logger = util.get_class_logger(self)
|
self._logger = util.get_class_logger(self)
|
||||||
|
@ -450,22 +348,18 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
||||||
self._preferred_client_max_window_bits = None
|
self._preferred_client_max_window_bits = None
|
||||||
self._client_no_context_takeover = False
|
self._client_no_context_takeover = False
|
||||||
|
|
||||||
self._draft08 = draft08
|
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
|
# This method returns "deflate" (not "permessage-deflate") for
|
||||||
|
# compatibility.
|
||||||
return 'deflate'
|
return 'deflate'
|
||||||
|
|
||||||
def _get_extension_response_internal(self):
|
def _get_extension_response_internal(self):
|
||||||
if self._draft08:
|
for name in self._request.get_parameter_names():
|
||||||
for name in self._request.get_parameter_names():
|
if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
|
||||||
if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
|
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
|
||||||
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
|
self._CLIENT_MAX_WINDOW_BITS_PARAM]:
|
||||||
self._CLIENT_MAX_WINDOW_BITS_PARAM]:
|
self._logger.debug('Unknown parameter: %r', name)
|
||||||
self._logger.debug('Unknown parameter: %r', name)
|
return None
|
||||||
return None
|
|
||||||
else:
|
|
||||||
# Any unknown parameter will be just ignored.
|
|
||||||
pass
|
|
||||||
|
|
||||||
server_max_window_bits = None
|
server_max_window_bits = None
|
||||||
if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
|
if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
|
||||||
|
@ -494,8 +388,7 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
||||||
# accept client_max_window_bits from a server or not.
|
# accept client_max_window_bits from a server or not.
|
||||||
client_client_max_window_bits = self._request.has_parameter(
|
client_client_max_window_bits = self._request.has_parameter(
|
||||||
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
||||||
if (self._draft08 and
|
if (client_client_max_window_bits and
|
||||||
client_client_max_window_bits and
|
|
||||||
self._request.get_parameter_value(
|
self._request.get_parameter_value(
|
||||||
self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
|
self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
|
||||||
self._logger.debug('%s parameter must not have a value in a '
|
self._logger.debug('%s parameter must not have a value in a '
|
||||||
|
@ -529,7 +422,7 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
||||||
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
|
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
|
||||||
|
|
||||||
if self._preferred_client_max_window_bits is not None:
|
if self._preferred_client_max_window_bits is not None:
|
||||||
if self._draft08 and not client_client_max_window_bits:
|
if not client_client_max_window_bits:
|
||||||
self._logger.debug('Processor is configured to use %s but '
|
self._logger.debug('Processor is configured to use %s but '
|
||||||
'the client cannot accept it',
|
'the client cannot accept it',
|
||||||
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
||||||
|
@ -765,33 +658,6 @@ _available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
|
||||||
_compression_extension_names.append('deflate')
|
_compression_extension_names.append('deflate')
|
||||||
|
|
||||||
|
|
||||||
class PerMessageCompressExtensionProcessor(
|
|
||||||
CompressionExtensionProcessorBase):
|
|
||||||
"""permessage-compress extension processor.
|
|
||||||
|
|
||||||
Specification:
|
|
||||||
http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression
|
|
||||||
"""
|
|
||||||
|
|
||||||
_DEFLATE_METHOD = 'deflate'
|
|
||||||
|
|
||||||
def __init__(self, request):
|
|
||||||
CompressionExtensionProcessorBase.__init__(self, request)
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return common.PERMESSAGE_COMPRESSION_EXTENSION
|
|
||||||
|
|
||||||
def _lookup_compression_processor(self, method_desc):
|
|
||||||
if method_desc.name() == self._DEFLATE_METHOD:
|
|
||||||
return PerMessageDeflateExtensionProcessor(method_desc, False)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = (
|
|
||||||
PerMessageCompressExtensionProcessor)
|
|
||||||
_compression_extension_names.append(common.PERMESSAGE_COMPRESSION_EXTENSION)
|
|
||||||
|
|
||||||
|
|
||||||
class MuxExtensionProcessor(ExtensionProcessorInterface):
|
class MuxExtensionProcessor(ExtensionProcessorInterface):
|
||||||
"""WebSocket multiplexing extension processor."""
|
"""WebSocket multiplexing extension processor."""
|
||||||
|
|
||||||
|
@ -825,10 +691,9 @@ class MuxExtensionProcessor(ExtensionProcessorInterface):
|
||||||
else:
|
else:
|
||||||
# Mux extension should not be applied before any history-based
|
# Mux extension should not be applied before any history-based
|
||||||
# compression extension.
|
# compression extension.
|
||||||
if (name == common.DEFLATE_FRAME_EXTENSION or
|
if (name == 'deflate' or
|
||||||
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or
|
name == common.DEFLATE_FRAME_EXTENSION or
|
||||||
name == common.PERMESSAGE_COMPRESSION_EXTENSION or
|
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
|
||||||
name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION):
|
|
||||||
self.set_active(False)
|
self.set_active(False)
|
||||||
return
|
return
|
||||||
|
|
|
@ -72,6 +72,7 @@ _PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
|
||||||
|
|
||||||
|
|
||||||
class ApacheLogHandler(logging.Handler):
|
class ApacheLogHandler(logging.Handler):
|
||||||
|
|
||||||
"""Wrapper logging.Handler to emit log message to apache's error.log."""
|
"""Wrapper logging.Handler to emit log message to apache's error.log."""
|
||||||
|
|
||||||
_LEVELS = {
|
_LEVELS = {
|
||||||
|
@ -136,6 +137,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _parse_option(name, value, definition):
|
def _parse_option(name, value, definition):
|
||||||
|
"""Return the meaning of a option value."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -147,6 +149,7 @@ def _parse_option(name, value, definition):
|
||||||
|
|
||||||
|
|
||||||
def _create_dispatcher():
|
def _create_dispatcher():
|
||||||
|
"""Initialize a dispatch.Dispatcher."""
|
||||||
_LOGGER.info('Initializing Dispatcher')
|
_LOGGER.info('Initializing Dispatcher')
|
||||||
|
|
||||||
options = apache.main_server.get_options()
|
options = apache.main_server.get_options()
|
||||||
|
@ -187,7 +190,6 @@ def headerparserhandler(request):
|
||||||
This function is named headerparserhandler because it is the default
|
This function is named headerparserhandler because it is the default
|
||||||
name for a PythonHeaderParserHandler.
|
name for a PythonHeaderParserHandler.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
handshake_is_done = False
|
handshake_is_done = False
|
||||||
try:
|
try:
|
||||||
# Fallback to default http handler for request paths for which
|
# Fallback to default http handler for request paths for which
|
||||||
|
@ -234,7 +236,8 @@ def headerparserhandler(request):
|
||||||
request._dispatcher = _dispatcher
|
request._dispatcher = _dispatcher
|
||||||
_dispatcher.transfer_data(request)
|
_dispatcher.transfer_data(request)
|
||||||
except handshake.AbortedByUserException, e:
|
except handshake.AbortedByUserException, e:
|
||||||
request.log_error('mod_pywebsocket: Aborted: %s' % e, apache.APLOG_INFO)
|
request.log_error('mod_pywebsocket: Aborted: %s' % e,
|
||||||
|
apache.APLOG_INFO)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
# DispatchException can also be thrown if something is wrong in
|
# DispatchException can also be thrown if something is wrong in
|
||||||
# pywebsocket code. It's caught here, then.
|
# pywebsocket code. It's caught here, then.
|
|
@ -40,6 +40,7 @@ import sys
|
||||||
|
|
||||||
|
|
||||||
class MemorizingFile(object):
|
class MemorizingFile(object):
|
||||||
|
|
||||||
"""MemorizingFile wraps a file and memorizes lines read by readline.
|
"""MemorizingFile wraps a file and memorizes lines read by readline.
|
||||||
|
|
||||||
Note that data read by other methods are not memorized. This behavior
|
Note that data read by other methods are not memorized. This behavior
|
||||||
|
@ -56,7 +57,6 @@ class MemorizingFile(object):
|
||||||
Only the first max_memorized_lines are memorized.
|
Only the first max_memorized_lines are memorized.
|
||||||
Default: sys.maxint.
|
Default: sys.maxint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._file = file_
|
self._file = file_
|
||||||
self._memorized_lines = []
|
self._memorized_lines = []
|
||||||
self._max_memorized_lines = max_memorized_lines
|
self._max_memorized_lines = max_memorized_lines
|
||||||
|
@ -64,6 +64,11 @@ class MemorizingFile(object):
|
||||||
self._buffered_line = None
|
self._buffered_line = None
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name):
|
||||||
|
"""Return a file attribute.
|
||||||
|
|
||||||
|
Returns the value overridden by this class for some attributes,
|
||||||
|
and forwards the call to _file for the other attributes.
|
||||||
|
"""
|
||||||
if name in ('_file', '_memorized_lines', '_max_memorized_lines',
|
if name in ('_file', '_memorized_lines', '_max_memorized_lines',
|
||||||
'_buffered', '_buffered_line', 'readline',
|
'_buffered', '_buffered_line', 'readline',
|
||||||
'get_memorized_lines'):
|
'get_memorized_lines'):
|
||||||
|
@ -77,7 +82,6 @@ class MemorizingFile(object):
|
||||||
the whole line will be read out from underlying file object by
|
the whole line will be read out from underlying file object by
|
||||||
subsequent readline calls.
|
subsequent readline calls.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._buffered:
|
if self._buffered:
|
||||||
line = self._buffered_line
|
line = self._buffered_line
|
||||||
self._buffered = False
|
self._buffered = False
|
|
@ -104,23 +104,31 @@ _DROP_CODE_BAD_FRAGMENTATION = 3009
|
||||||
|
|
||||||
|
|
||||||
class MuxUnexpectedException(Exception):
|
class MuxUnexpectedException(Exception):
|
||||||
|
|
||||||
"""Exception in handling multiplexing extension."""
|
"""Exception in handling multiplexing extension."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Temporary
|
# Temporary
|
||||||
class MuxNotImplementedException(Exception):
|
class MuxNotImplementedException(Exception):
|
||||||
|
|
||||||
"""Raised when a flow enters unimplemented code path."""
|
"""Raised when a flow enters unimplemented code path."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LogicalConnectionClosedException(Exception):
|
class LogicalConnectionClosedException(Exception):
|
||||||
|
|
||||||
"""Raised when logical connection is gracefully closed."""
|
"""Raised when logical connection is gracefully closed."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PhysicalConnectionError(Exception):
|
class PhysicalConnectionError(Exception):
|
||||||
|
|
||||||
"""Raised when there is a physical connection error."""
|
"""Raised when there is a physical connection error."""
|
||||||
|
|
||||||
def __init__(self, drop_code, message=''):
|
def __init__(self, drop_code, message=''):
|
||||||
super(PhysicalConnectionError, self).__init__(
|
super(PhysicalConnectionError, self).__init__(
|
||||||
'code=%d, message=%r' % (drop_code, message))
|
'code=%d, message=%r' % (drop_code, message))
|
||||||
|
@ -129,8 +137,11 @@ class PhysicalConnectionError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class LogicalChannelError(Exception):
|
class LogicalChannelError(Exception):
|
||||||
|
|
||||||
"""Raised when there is a logical channel error."""
|
"""Raised when there is a logical channel error."""
|
||||||
|
|
||||||
def __init__(self, channel_id, drop_code, message=''):
|
def __init__(self, channel_id, drop_code, message=''):
|
||||||
|
"""Initialize the error with a status message."""
|
||||||
super(LogicalChannelError, self).__init__(
|
super(LogicalChannelError, self).__init__(
|
||||||
'channel_id=%d, code=%d, message=%r' % (
|
'channel_id=%d, code=%d, message=%r' % (
|
||||||
channel_id, drop_code, message))
|
channel_id, drop_code, message))
|
||||||
|
@ -181,7 +192,7 @@ def _create_drop_channel(channel_id, code=None, message=''):
|
||||||
first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
|
first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
|
||||||
block = chr(first_byte) + _encode_channel_id(channel_id)
|
block = chr(first_byte) + _encode_channel_id(channel_id)
|
||||||
if code is None:
|
if code is None:
|
||||||
block += _encode_number(0) # Reason size
|
block += _encode_number(0) # Reason size
|
||||||
else:
|
else:
|
||||||
reason = struct.pack('!H', code) + message
|
reason = struct.pack('!H', code) + message
|
||||||
reason_size = _encode_number(len(reason))
|
reason_size = _encode_number(len(reason))
|
||||||
|
@ -209,7 +220,7 @@ def _create_new_channel_slot(slots, send_quota):
|
||||||
|
|
||||||
|
|
||||||
def _create_fallback_new_channel_slot():
|
def _create_fallback_new_channel_slot():
|
||||||
first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
|
first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
|
||||||
block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
|
block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
|
||||||
return block
|
return block
|
||||||
|
|
||||||
|
@ -232,7 +243,9 @@ def _parse_request_text(request_text):
|
||||||
|
|
||||||
|
|
||||||
class _ControlBlock(object):
|
class _ControlBlock(object):
|
||||||
|
|
||||||
"""A structure that holds parsing result of multiplexing control block.
|
"""A structure that holds parsing result of multiplexing control block.
|
||||||
|
|
||||||
Control block specific attributes will be added by _MuxFramePayloadParser.
|
Control block specific attributes will be added by _MuxFramePayloadParser.
|
||||||
(e.g. encoded_handshake will be added for AddChannelRequest and
|
(e.g. encoded_handshake will be added for AddChannelRequest and
|
||||||
AddChannelResponse)
|
AddChannelResponse)
|
||||||
|
@ -243,6 +256,7 @@ class _ControlBlock(object):
|
||||||
|
|
||||||
|
|
||||||
class _MuxFramePayloadParser(object):
|
class _MuxFramePayloadParser(object):
|
||||||
|
|
||||||
"""A class that parses multiplexed frame payload."""
|
"""A class that parses multiplexed frame payload."""
|
||||||
|
|
||||||
def __init__(self, payload):
|
def __init__(self, payload):
|
||||||
|
@ -251,13 +265,12 @@ class _MuxFramePayloadParser(object):
|
||||||
self._logger = util.get_class_logger(self)
|
self._logger = util.get_class_logger(self)
|
||||||
|
|
||||||
def read_channel_id(self):
|
def read_channel_id(self):
|
||||||
"""Reads channel id.
|
"""Read channel id.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: when the payload doesn't contain
|
ValueError: when the payload doesn't contain
|
||||||
valid channel id.
|
valid channel id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
remaining_length = len(self._data) - self._read_position
|
remaining_length = len(self._data) - self._read_position
|
||||||
pos = self._read_position
|
pos = self._read_position
|
||||||
if remaining_length == 0:
|
if remaining_length == 0:
|
||||||
|
@ -288,12 +301,11 @@ class _MuxFramePayloadParser(object):
|
||||||
return channel_id
|
return channel_id
|
||||||
|
|
||||||
def read_inner_frame(self):
|
def read_inner_frame(self):
|
||||||
"""Reads an inner frame.
|
"""Read an inner frame.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
PhysicalConnectionError: when the inner frame is invalid.
|
PhysicalConnectionError: when the inner frame is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self._data) == self._read_position:
|
if len(self._data) == self._read_position:
|
||||||
raise PhysicalConnectionError(
|
raise PhysicalConnectionError(
|
||||||
_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
|
_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
|
||||||
|
@ -345,12 +357,11 @@ class _MuxFramePayloadParser(object):
|
||||||
return number
|
return number
|
||||||
|
|
||||||
def _read_size_and_contents(self):
|
def _read_size_and_contents(self):
|
||||||
"""Reads data that consists of followings:
|
"""Read data that consists of the following:
|
||||||
- the size of the contents encoded the same way as payload length
|
- the size of the contents encoded the same way as payload length
|
||||||
of the WebSocket Protocol with 1 bit padding at the head.
|
of the WebSocket Protocol with 1 bit padding at the head.
|
||||||
- the contents.
|
- the contents.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
size = self._read_number()
|
size = self._read_number()
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
|
@ -455,14 +466,13 @@ class _MuxFramePayloadParser(object):
|
||||||
return control_block
|
return control_block
|
||||||
|
|
||||||
def read_control_blocks(self):
|
def read_control_blocks(self):
|
||||||
"""Reads control block(s).
|
"""Read control block(s).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
PhysicalConnectionError: when the payload contains invalid control
|
PhysicalConnectionError: when the payload contains invalid control
|
||||||
block(s).
|
block(s).
|
||||||
StopIteration: when no control blocks left.
|
StopIteration: when no control blocks left.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while self._read_position < len(self._data):
|
while self._read_position < len(self._data):
|
||||||
first_byte = ord(self._data[self._read_position])
|
first_byte = ord(self._data[self._read_position])
|
||||||
self._read_position += 1
|
self._read_position += 1
|
||||||
|
@ -488,17 +498,17 @@ class _MuxFramePayloadParser(object):
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
def remaining_data(self):
|
def remaining_data(self):
|
||||||
"""Returns remaining data."""
|
"""Return remaining data."""
|
||||||
|
|
||||||
return self._data[self._read_position:]
|
return self._data[self._read_position:]
|
||||||
|
|
||||||
|
|
||||||
class _LogicalRequest(object):
|
class _LogicalRequest(object):
|
||||||
|
|
||||||
"""Mimics mod_python request."""
|
"""Mimics mod_python request."""
|
||||||
|
|
||||||
def __init__(self, channel_id, command, path, protocol, headers,
|
def __init__(self, channel_id, command, path, protocol, headers,
|
||||||
connection):
|
connection):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
channel_id: the channel id of the logical channel.
|
channel_id: the channel id of the logical channel.
|
||||||
|
@ -507,7 +517,6 @@ class _LogicalRequest(object):
|
||||||
headers: HTTP headers.
|
headers: HTTP headers.
|
||||||
connection: _LogicalConnection instance.
|
connection: _LogicalConnection instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.channel_id = channel_id
|
self.channel_id = channel_id
|
||||||
self.method = command
|
self.method = command
|
||||||
self.uri = path
|
self.uri = path
|
||||||
|
@ -518,14 +527,16 @@ class _LogicalRequest(object):
|
||||||
self.client_terminated = False
|
self.client_terminated = False
|
||||||
|
|
||||||
def is_https(self):
|
def is_https(self):
|
||||||
"""Mimics request.is_https(). Returns False because this method is
|
"""Mimic request.is_https().
|
||||||
used only by old protocols (hixie and hybi00).
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
Returns False because this method is used only by old protocols
|
||||||
|
(hixie and hybi00).
|
||||||
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class _LogicalConnection(object):
|
class _LogicalConnection(object):
|
||||||
|
|
||||||
"""Mimics mod_python mp_conn."""
|
"""Mimics mod_python mp_conn."""
|
||||||
|
|
||||||
# For details, see the comment of set_read_state().
|
# For details, see the comment of set_read_state().
|
||||||
|
@ -534,13 +545,12 @@ class _LogicalConnection(object):
|
||||||
STATE_TERMINATED = 3
|
STATE_TERMINATED = 3
|
||||||
|
|
||||||
def __init__(self, mux_handler, channel_id):
|
def __init__(self, mux_handler, channel_id):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mux_handler: _MuxHandler instance.
|
mux_handler: _MuxHandler instance.
|
||||||
channel_id: channel id of this connection.
|
channel_id: channel id of this connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._mux_handler = mux_handler
|
self._mux_handler = mux_handler
|
||||||
self._channel_id = channel_id
|
self._channel_id = channel_id
|
||||||
self._incoming_data = ''
|
self._incoming_data = ''
|
||||||
|
@ -555,25 +565,23 @@ class _LogicalConnection(object):
|
||||||
|
|
||||||
def get_local_addr(self):
|
def get_local_addr(self):
|
||||||
"""Getter to mimic mp_conn.local_addr."""
|
"""Getter to mimic mp_conn.local_addr."""
|
||||||
|
|
||||||
return self._mux_handler.physical_connection.get_local_addr()
|
return self._mux_handler.physical_connection.get_local_addr()
|
||||||
local_addr = property(get_local_addr)
|
local_addr = property(get_local_addr)
|
||||||
|
|
||||||
def get_remote_addr(self):
|
def get_remote_addr(self):
|
||||||
"""Getter to mimic mp_conn.remote_addr."""
|
"""Getter to mimic mp_conn.remote_addr."""
|
||||||
|
|
||||||
return self._mux_handler.physical_connection.get_remote_addr()
|
return self._mux_handler.physical_connection.get_remote_addr()
|
||||||
remote_addr = property(get_remote_addr)
|
remote_addr = property(get_remote_addr)
|
||||||
|
|
||||||
def get_memorized_lines(self):
|
def get_memorized_lines(self):
|
||||||
"""Gets memorized lines. Not supported."""
|
"""Get memorized lines. Not supported."""
|
||||||
|
|
||||||
raise MuxUnexpectedException('_LogicalConnection does not support '
|
raise MuxUnexpectedException('_LogicalConnection does not support '
|
||||||
'get_memorized_lines')
|
'get_memorized_lines')
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
"""Writes data. mux_handler sends data asynchronously. The caller will
|
"""Write data. mux_handler sends data asynchronously.
|
||||||
be suspended until write done.
|
|
||||||
|
The caller will be suspended until write done.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: data to be written.
|
data: data to be written.
|
||||||
|
@ -582,7 +590,6 @@ class _LogicalConnection(object):
|
||||||
MuxUnexpectedException: when called before finishing the previous
|
MuxUnexpectedException: when called before finishing the previous
|
||||||
write.
|
write.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._write_condition.acquire()
|
self._write_condition.acquire()
|
||||||
if self._waiting_write_completion:
|
if self._waiting_write_completion:
|
||||||
|
@ -598,18 +605,18 @@ class _LogicalConnection(object):
|
||||||
self._write_condition.release()
|
self._write_condition.release()
|
||||||
|
|
||||||
def write_control_data(self, data):
|
def write_control_data(self, data):
|
||||||
"""Writes data via the control channel. Don't wait finishing write
|
"""Write data via the control channel.
|
||||||
because this method can be called by mux dispatcher.
|
|
||||||
|
Don't wait finishing write because this method can be called by
|
||||||
|
mux dispatcher.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: data to be written.
|
data: data to be written.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._mux_handler.send_control_data(data)
|
self._mux_handler.send_control_data(data)
|
||||||
|
|
||||||
def on_write_data_done(self):
|
def on_write_data_done(self):
|
||||||
"""Called when sending data is completed."""
|
"""Called when sending data is completed."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._write_condition.acquire()
|
self._write_condition.acquire()
|
||||||
if not self._waiting_write_completion:
|
if not self._waiting_write_completion:
|
||||||
|
@ -623,7 +630,6 @@ class _LogicalConnection(object):
|
||||||
|
|
||||||
def on_writer_done(self):
|
def on_writer_done(self):
|
||||||
"""Called by the mux handler when the writer thread has finished."""
|
"""Called by the mux handler when the writer thread has finished."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._write_condition.acquire()
|
self._write_condition.acquire()
|
||||||
self._waiting_write_completion = False
|
self._waiting_write_completion = False
|
||||||
|
@ -631,23 +637,24 @@ class _LogicalConnection(object):
|
||||||
finally:
|
finally:
|
||||||
self._write_condition.release()
|
self._write_condition.release()
|
||||||
|
|
||||||
|
|
||||||
def append_frame_data(self, frame_data):
|
def append_frame_data(self, frame_data):
|
||||||
"""Appends incoming frame data. Called when mux_handler dispatches
|
"""Append incoming frame data.
|
||||||
frame data to the corresponding application.
|
|
||||||
|
Called when mux_handler dispatches frame data to the corresponding
|
||||||
|
application.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
frame_data: incoming frame data.
|
frame_data: incoming frame data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._read_condition.acquire()
|
self._read_condition.acquire()
|
||||||
self._incoming_data += frame_data
|
self._incoming_data += frame_data
|
||||||
self._read_condition.notify()
|
self._read_condition.notify()
|
||||||
self._read_condition.release()
|
self._read_condition.release()
|
||||||
|
|
||||||
def read(self, length):
|
def read(self, length):
|
||||||
"""Reads data. Blocks until enough data has arrived via physical
|
"""Read data.
|
||||||
connection.
|
|
||||||
|
Blocks until enough data has arrived via physical connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
length: length of data to be read.
|
length: length of data to be read.
|
||||||
|
@ -657,7 +664,6 @@ class _LogicalConnection(object):
|
||||||
ConnectionTerminatedException: when the physical connection has
|
ConnectionTerminatedException: when the physical connection has
|
||||||
closed, or an error is caused on the reader thread.
|
closed, or an error is caused on the reader thread.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._read_condition.acquire()
|
self._read_condition.acquire()
|
||||||
while (self._read_state == self.STATE_ACTIVE and
|
while (self._read_state == self.STATE_ACTIVE and
|
||||||
len(self._incoming_data) < length):
|
len(self._incoming_data) < length):
|
||||||
|
@ -680,9 +686,9 @@ class _LogicalConnection(object):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def set_read_state(self, new_state):
|
def set_read_state(self, new_state):
|
||||||
"""Sets the state of this connection. Called when an event for this
|
"""Set the state of this connection.
|
||||||
connection has occurred.
|
|
||||||
|
|
||||||
|
Called when an event for this connection has occurred.
|
||||||
Args:
|
Args:
|
||||||
new_state: state to be set. new_state must be one of followings:
|
new_state: state to be set. new_state must be one of followings:
|
||||||
- STATE_GRACEFULLY_CLOSED: when closing handshake for this
|
- STATE_GRACEFULLY_CLOSED: when closing handshake for this
|
||||||
|
@ -690,7 +696,6 @@ class _LogicalConnection(object):
|
||||||
- STATE_TERMINATED: when the physical connection has closed or
|
- STATE_TERMINATED: when the physical connection has closed or
|
||||||
DropChannel of this connection has received.
|
DropChannel of this connection has received.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._read_condition.acquire()
|
self._read_condition.acquire()
|
||||||
self._read_state = new_state
|
self._read_state = new_state
|
||||||
self._read_condition.notify()
|
self._read_condition.notify()
|
||||||
|
@ -698,8 +703,8 @@ class _LogicalConnection(object):
|
||||||
|
|
||||||
|
|
||||||
class _InnerMessage(object):
|
class _InnerMessage(object):
|
||||||
"""Holds the result of _InnerMessageBuilder.build().
|
|
||||||
"""
|
"""Hold the result of _InnerMessageBuilder.build()."""
|
||||||
|
|
||||||
def __init__(self, opcode, payload):
|
def __init__(self, opcode, payload):
|
||||||
self.opcode = opcode
|
self.opcode = opcode
|
||||||
|
@ -707,7 +712,10 @@ class _InnerMessage(object):
|
||||||
|
|
||||||
|
|
||||||
class _InnerMessageBuilder(object):
|
class _InnerMessageBuilder(object):
|
||||||
"""A class that holds the context of inner message fragmentation and
|
|
||||||
|
"""Class to build an _InnerMessage.
|
||||||
|
|
||||||
|
A class that holds the context of inner message fragmentation and
|
||||||
builds a message from fragmented inner frame(s).
|
builds a message from fragmented inner frame(s).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -791,8 +799,10 @@ class _InnerMessageBuilder(object):
|
||||||
return _InnerMessage(opcode, payload)
|
return _InnerMessage(opcode, payload)
|
||||||
|
|
||||||
def build(self, frame):
|
def build(self, frame):
|
||||||
"""Build an inner message. Returns an _InnerMessage instance when
|
"""Build an inner message.
|
||||||
the given frame is the last fragmented frame. Returns None otherwise.
|
|
||||||
|
Returns an _InnerMessage instance when the given frame is the last
|
||||||
|
fragmented frame. Returns None otherwise.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
frame: an inner frame.
|
frame: an inner frame.
|
||||||
|
@ -801,17 +811,18 @@ class _InnerMessageBuilder(object):
|
||||||
receiving non continuation data opcode but the fin flag of
|
receiving non continuation data opcode but the fin flag of
|
||||||
the previous inner frame was not set.)
|
the previous inner frame was not set.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._frame_handler(frame)
|
return self._frame_handler(frame)
|
||||||
|
|
||||||
|
|
||||||
class _LogicalStream(Stream):
|
class _LogicalStream(Stream):
|
||||||
"""Mimics the Stream class. This class interprets multiplexed WebSocket
|
|
||||||
frames.
|
"""Mimics the Stream class.
|
||||||
|
|
||||||
|
This class interprets multiplexed WebSocket frames.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, request, stream_options, send_quota, receive_quota):
|
def __init__(self, request, stream_options, send_quota, receive_quota):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: _LogicalRequest instance.
|
request: _LogicalRequest instance.
|
||||||
|
@ -819,7 +830,6 @@ class _LogicalStream(Stream):
|
||||||
send_quota: Initial send quota.
|
send_quota: Initial send quota.
|
||||||
receive_quota: Initial receive quota.
|
receive_quota: Initial receive quota.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Physical stream is responsible for masking.
|
# Physical stream is responsible for masking.
|
||||||
stream_options.unmask_receive = False
|
stream_options.unmask_receive = False
|
||||||
Stream.__init__(self, request, stream_options)
|
Stream.__init__(self, request, stream_options)
|
||||||
|
@ -928,7 +938,6 @@ class _LogicalStream(Stream):
|
||||||
|
|
||||||
def replenish_send_quota(self, send_quota):
|
def replenish_send_quota(self, send_quota):
|
||||||
"""Replenish send quota."""
|
"""Replenish send quota."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._send_condition.acquire()
|
self._send_condition.acquire()
|
||||||
if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
|
if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
|
||||||
|
@ -943,8 +952,7 @@ class _LogicalStream(Stream):
|
||||||
self._send_condition.release()
|
self._send_condition.release()
|
||||||
|
|
||||||
def consume_receive_quota(self, amount):
|
def consume_receive_quota(self, amount):
|
||||||
"""Consumes receive quota. Returns False on failure."""
|
"""Consume receive quota. Returns False on failure."""
|
||||||
|
|
||||||
if self._receive_quota < amount:
|
if self._receive_quota < amount:
|
||||||
self._logger.debug('Violate quota on channel id %d: %d < %d' %
|
self._logger.debug('Violate quota on channel id %d: %d < %d' %
|
||||||
(self._request.channel_id,
|
(self._request.channel_id,
|
||||||
|
@ -955,7 +963,6 @@ class _LogicalStream(Stream):
|
||||||
|
|
||||||
def send_message(self, message, end=True, binary=False):
|
def send_message(self, message, end=True, binary=False):
|
||||||
"""Override Stream.send_message."""
|
"""Override Stream.send_message."""
|
||||||
|
|
||||||
if self._request.server_terminated:
|
if self._request.server_terminated:
|
||||||
raise BadOperationException(
|
raise BadOperationException(
|
||||||
'Requested send_message after sending out a closing handshake')
|
'Requested send_message after sending out a closing handshake')
|
||||||
|
@ -985,14 +992,13 @@ class _LogicalStream(Stream):
|
||||||
self._last_message_was_fragmented = not end
|
self._last_message_was_fragmented = not end
|
||||||
|
|
||||||
def _receive_frame(self):
|
def _receive_frame(self):
|
||||||
"""Overrides Stream._receive_frame.
|
"""Override Stream._receive_frame.
|
||||||
|
|
||||||
In addition to call Stream._receive_frame, this method adds the amount
|
In addition to call Stream._receive_frame, this method adds the amount
|
||||||
of payload to receiving quota and sends FlowControl to the client.
|
of payload to receiving quota and sends FlowControl to the client.
|
||||||
We need to do it here because Stream.receive_message() handles
|
We need to do it here because Stream.receive_message() handles
|
||||||
control frames internally.
|
control frames internally.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
|
opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
|
||||||
amount = len(payload)
|
amount = len(payload)
|
||||||
# Replenish extra one octet when receiving the first fragmented frame.
|
# Replenish extra one octet when receiving the first fragmented frame.
|
||||||
|
@ -1007,9 +1013,7 @@ class _LogicalStream(Stream):
|
||||||
return opcode, payload, fin, rsv1, rsv2, rsv3
|
return opcode, payload, fin, rsv1, rsv2, rsv3
|
||||||
|
|
||||||
def _get_message_from_frame(self, frame):
|
def _get_message_from_frame(self, frame):
|
||||||
"""Overrides Stream._get_message_from_frame.
|
"""Override Stream._get_message_from_frame."""
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
inner_message = self._inner_message_builder.build(frame)
|
inner_message = self._inner_message_builder.build(frame)
|
||||||
except InvalidFrameException:
|
except InvalidFrameException:
|
||||||
|
@ -1022,8 +1026,7 @@ class _LogicalStream(Stream):
|
||||||
return inner_message.payload
|
return inner_message.payload
|
||||||
|
|
||||||
def receive_message(self):
|
def receive_message(self):
|
||||||
"""Overrides Stream.receive_message."""
|
"""Override Stream.receive_message."""
|
||||||
|
|
||||||
# Just call Stream.receive_message(), but catch
|
# Just call Stream.receive_message(), but catch
|
||||||
# LogicalConnectionClosedException, which is raised when the logical
|
# LogicalConnectionClosedException, which is raised when the logical
|
||||||
# connection has closed gracefully.
|
# connection has closed gracefully.
|
||||||
|
@ -1034,8 +1037,7 @@ class _LogicalStream(Stream):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _send_closing_handshake(self, code, reason):
|
def _send_closing_handshake(self, code, reason):
|
||||||
"""Overrides Stream._send_closing_handshake."""
|
"""Override Stream._send_closing_handshake."""
|
||||||
|
|
||||||
body = create_closing_handshake_body(code, reason)
|
body = create_closing_handshake_body(code, reason)
|
||||||
self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
|
self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
|
||||||
(self._request.channel_id, code, reason))
|
(self._request.channel_id, code, reason))
|
||||||
|
@ -1044,8 +1046,7 @@ class _LogicalStream(Stream):
|
||||||
self._request.server_terminated = True
|
self._request.server_terminated = True
|
||||||
|
|
||||||
def send_ping(self, body=''):
|
def send_ping(self, body=''):
|
||||||
"""Overrides Stream.send_ping"""
|
"""Override Stream.send_ping."""
|
||||||
|
|
||||||
self._logger.debug('Sending ping on logical channel %d: %r' %
|
self._logger.debug('Sending ping on logical channel %d: %r' %
|
||||||
(self._request.channel_id, body))
|
(self._request.channel_id, body))
|
||||||
self._write_inner_frame(common.OPCODE_PING, body, end=True)
|
self._write_inner_frame(common.OPCODE_PING, body, end=True)
|
||||||
|
@ -1053,23 +1054,20 @@ class _LogicalStream(Stream):
|
||||||
self._ping_queue.append(body)
|
self._ping_queue.append(body)
|
||||||
|
|
||||||
def _send_pong(self, body):
|
def _send_pong(self, body):
|
||||||
"""Overrides Stream._send_pong"""
|
"""Override Stream._send_pong."""
|
||||||
|
|
||||||
self._logger.debug('Sending pong on logical channel %d: %r' %
|
self._logger.debug('Sending pong on logical channel %d: %r' %
|
||||||
(self._request.channel_id, body))
|
(self._request.channel_id, body))
|
||||||
self._write_inner_frame(common.OPCODE_PONG, body, end=True)
|
self._write_inner_frame(common.OPCODE_PONG, body, end=True)
|
||||||
|
|
||||||
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
|
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
|
||||||
"""Overrides Stream.close_connection."""
|
"""Override Stream.close_connection."""
|
||||||
|
|
||||||
# TODO(bashi): Implement
|
# TODO(bashi): Implement
|
||||||
self._logger.debug('Closing logical connection %d' %
|
self._logger.debug('Closing logical connection %d' %
|
||||||
self._request.channel_id)
|
self._request.channel_id)
|
||||||
self._request.server_terminated = True
|
self._request.server_terminated = True
|
||||||
|
|
||||||
def stop_sending(self):
|
def stop_sending(self):
|
||||||
"""Stops accepting new send operation (_write_inner_frame)."""
|
"""Stop accepting new send operation (_write_inner_frame)."""
|
||||||
|
|
||||||
self._send_condition.acquire()
|
self._send_condition.acquire()
|
||||||
self._send_closed = True
|
self._send_closed = True
|
||||||
self._send_condition.notify()
|
self._send_condition.notify()
|
||||||
|
@ -1077,7 +1075,10 @@ class _LogicalStream(Stream):
|
||||||
|
|
||||||
|
|
||||||
class _OutgoingData(object):
|
class _OutgoingData(object):
|
||||||
"""A structure that holds data to be sent via physical connection and
|
|
||||||
|
"""Simple data/channel container.
|
||||||
|
|
||||||
|
A structure that holds data to be sent via physical connection and
|
||||||
origin of the data.
|
origin of the data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1087,6 +1088,7 @@ class _OutgoingData(object):
|
||||||
|
|
||||||
|
|
||||||
class _PhysicalConnectionWriter(threading.Thread):
|
class _PhysicalConnectionWriter(threading.Thread):
|
||||||
|
|
||||||
"""A thread that is responsible for writing data to physical connection.
|
"""A thread that is responsible for writing data to physical connection.
|
||||||
|
|
||||||
TODO(bashi): Make sure there is no thread-safety problem when the reader
|
TODO(bashi): Make sure there is no thread-safety problem when the reader
|
||||||
|
@ -1094,12 +1096,11 @@ class _PhysicalConnectionWriter(threading.Thread):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mux_handler):
|
def __init__(self, mux_handler):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mux_handler: _MuxHandler instance.
|
mux_handler: _MuxHandler instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self._logger = util.get_class_logger(self)
|
self._logger = util.get_class_logger(self)
|
||||||
self._mux_handler = mux_handler
|
self._mux_handler = mux_handler
|
||||||
|
@ -1118,16 +1119,14 @@ class _PhysicalConnectionWriter(threading.Thread):
|
||||||
self._deque_condition = threading.Condition()
|
self._deque_condition = threading.Condition()
|
||||||
|
|
||||||
def put_outgoing_data(self, data):
|
def put_outgoing_data(self, data):
|
||||||
"""Puts outgoing data.
|
"""Put outgoing data.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: _OutgoingData instance.
|
data: _OutgoingData instance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
BadOperationException: when the thread has been requested to
|
BadOperationException: when the thread has been requested to
|
||||||
terminate.
|
terminate.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._deque_condition.acquire()
|
self._deque_condition.acquire()
|
||||||
if self._stop_requested:
|
if self._stop_requested:
|
||||||
|
@ -1193,8 +1192,7 @@ class _PhysicalConnectionWriter(threading.Thread):
|
||||||
self._mux_handler.notify_writer_done()
|
self._mux_handler.notify_writer_done()
|
||||||
|
|
||||||
def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
|
def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
|
||||||
"""Stops the writer thread."""
|
"""Stop the writer thread."""
|
||||||
|
|
||||||
self._deque_condition.acquire()
|
self._deque_condition.acquire()
|
||||||
self._stop_requested = True
|
self._stop_requested = True
|
||||||
self._close_code = close_code
|
self._close_code = close_code
|
||||||
|
@ -1203,16 +1201,16 @@ class _PhysicalConnectionWriter(threading.Thread):
|
||||||
|
|
||||||
|
|
||||||
class _PhysicalConnectionReader(threading.Thread):
|
class _PhysicalConnectionReader(threading.Thread):
|
||||||
|
|
||||||
"""A thread that is responsible for reading data from physical connection.
|
"""A thread that is responsible for reading data from physical connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mux_handler):
|
def __init__(self, mux_handler):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mux_handler: _MuxHandler instance.
|
mux_handler: _MuxHandler instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self._logger = util.get_class_logger(self)
|
self._logger = util.get_class_logger(self)
|
||||||
self._mux_handler = mux_handler
|
self._mux_handler = mux_handler
|
||||||
|
@ -1254,18 +1252,18 @@ class _PhysicalConnectionReader(threading.Thread):
|
||||||
|
|
||||||
|
|
||||||
class _Worker(threading.Thread):
|
class _Worker(threading.Thread):
|
||||||
|
|
||||||
"""A thread that is responsible for running the corresponding application
|
"""A thread that is responsible for running the corresponding application
|
||||||
handler.
|
handler.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mux_handler, request):
|
def __init__(self, mux_handler, request):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mux_handler: _MuxHandler instance.
|
mux_handler: _MuxHandler instance.
|
||||||
request: _LogicalRequest instance.
|
request: _LogicalRequest instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self._logger = util.get_class_logger(self)
|
self._logger = util.get_class_logger(self)
|
||||||
self._mux_handler = mux_handler
|
self._mux_handler = mux_handler
|
||||||
|
@ -1286,19 +1284,20 @@ class _Worker(threading.Thread):
|
||||||
|
|
||||||
|
|
||||||
class _MuxHandshaker(hybi.Handshaker):
|
class _MuxHandshaker(hybi.Handshaker):
|
||||||
|
|
||||||
"""Opening handshake processor for multiplexing."""
|
"""Opening handshake processor for multiplexing."""
|
||||||
|
|
||||||
_DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
|
_DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
|
||||||
|
|
||||||
def __init__(self, request, dispatcher, send_quota, receive_quota):
|
def __init__(self, request, dispatcher, send_quota, receive_quota):
|
||||||
"""Constructs an instance.
|
"""Construct an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: _LogicalRequest instance.
|
request: _LogicalRequest instance.
|
||||||
dispatcher: Dispatcher instance (dispatch.Dispatcher).
|
dispatcher: Dispatcher instance (dispatch.Dispatcher).
|
||||||
send_quota: Initial send quota.
|
send_quota: Initial send quota.
|
||||||
receive_quota: Initial receive quota.
|
receive_quota: Initial receive quota.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hybi.Handshaker.__init__(self, request, dispatcher)
|
hybi.Handshaker.__init__(self, request, dispatcher)
|
||||||
self._send_quota = send_quota
|
self._send_quota = send_quota
|
||||||
self._receive_quota = receive_quota
|
self._receive_quota = receive_quota
|
||||||
|
@ -1316,7 +1315,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
||||||
|
|
||||||
def _create_stream(self, stream_options):
|
def _create_stream(self, stream_options):
|
||||||
"""Override hybi.Handshaker._create_stream."""
|
"""Override hybi.Handshaker._create_stream."""
|
||||||
|
|
||||||
self._logger.debug('Creating logical stream for %d' %
|
self._logger.debug('Creating logical stream for %d' %
|
||||||
self._request.channel_id)
|
self._request.channel_id)
|
||||||
return _LogicalStream(
|
return _LogicalStream(
|
||||||
|
@ -1325,7 +1323,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
||||||
|
|
||||||
def _create_handshake_response(self, accept):
|
def _create_handshake_response(self, accept):
|
||||||
"""Override hybi._create_handshake_response."""
|
"""Override hybi._create_handshake_response."""
|
||||||
|
|
||||||
response = []
|
response = []
|
||||||
|
|
||||||
response.append('HTTP/1.1 101 Switching Protocols\r\n')
|
response.append('HTTP/1.1 101 Switching Protocols\r\n')
|
||||||
|
@ -1348,7 +1345,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
||||||
|
|
||||||
def _send_handshake(self, accept):
|
def _send_handshake(self, accept):
|
||||||
"""Override hybi.Handshaker._send_handshake."""
|
"""Override hybi.Handshaker._send_handshake."""
|
||||||
|
|
||||||
# Don't send handshake response for the default channel
|
# Don't send handshake response for the default channel
|
||||||
if self._request.channel_id == _DEFAULT_CHANNEL_ID:
|
if self._request.channel_id == _DEFAULT_CHANNEL_ID:
|
||||||
return
|
return
|
||||||
|
@ -1363,8 +1359,8 @@ class _MuxHandshaker(hybi.Handshaker):
|
||||||
|
|
||||||
|
|
||||||
class _LogicalChannelData(object):
|
class _LogicalChannelData(object):
|
||||||
"""A structure that holds information about logical channel.
|
|
||||||
"""
|
"""A structure that holds information about logical channel."""
|
||||||
|
|
||||||
def __init__(self, request, worker):
|
def __init__(self, request, worker):
|
||||||
self.request = request
|
self.request = request
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue