mirror of
https://github.com/servo/servo.git
synced 2025-07-03 13:33:39 +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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": [
|
||||
[
|
||||
{}
|
||||
|
@ -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-margins-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-root-scroller-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": [
|
||||
[
|
||||
{}
|
||||
|
@ -271730,6 +271643,11 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"fetch/corb/resources/subframe-that-posts-html-containing-blob-url-to-parent.html": [
|
||||
[
|
||||
{}
|
||||
]
|
||||
],
|
||||
"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": [
|
||||
[
|
||||
{}
|
||||
|
@ -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": [
|
||||
[
|
||||
{}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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-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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -414540,6 +414498,18 @@
|
|||
"e2b95f87aee2b09233d324c898e02629ff83f8ce",
|
||||
"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": [
|
||||
"ed1c313412f5af0134a36fe72fa75b566c83d555",
|
||||
"support"
|
||||
|
@ -501641,7 +501611,7 @@
|
|||
"support"
|
||||
],
|
||||
"css/css-paint-api/registered-properties-in-custom-paint.https.html": [
|
||||
"de1e6dabe19bdd355060a62c7d697c64f49929fe",
|
||||
"dc51ea1e448fc91b632363d5ea394f5a4a90bd06",
|
||||
"reftest"
|
||||
],
|
||||
"css/css-paint-api/resources/html5.png": [
|
||||
|
@ -501653,7 +501623,7 @@
|
|||
"support"
|
||||
],
|
||||
"css/css-paint-api/style-background-image.https.html": [
|
||||
"c3359a8ee23b9c3d9007066235aef894ddbaf47f",
|
||||
"18660b13079289c93431e7ca6111d1c6c7adca80",
|
||||
"reftest"
|
||||
],
|
||||
"css/css-paint-api/style-before-pseudo-ref.html": [
|
||||
|
@ -501661,7 +501631,7 @@
|
|||
"support"
|
||||
],
|
||||
"css/css-paint-api/style-before-pseudo.https.html": [
|
||||
"cd16dfc6e532851b23abdf47cb4cc89f20a6c655",
|
||||
"a46f84e13e91ea23129a557893fcb929d6e191ed",
|
||||
"reftest"
|
||||
],
|
||||
"css/css-paint-api/style-first-letter-pseudo-ref.html": [
|
||||
|
@ -501669,7 +501639,7 @@
|
|||
"support"
|
||||
],
|
||||
"css/css-paint-api/style-first-letter-pseudo.https.html": [
|
||||
"996d9f2e93bc8ba0a54415d0ffcab4cdb7c1ffa7",
|
||||
"180ec110178f0cf9635b19309abb416e79ebf135",
|
||||
"reftest"
|
||||
],
|
||||
"css/css-paint-api/valid-image-after-load-ref.html": [
|
||||
|
@ -501796,13 +501766,9 @@
|
|||
"a06a40f39b4a748c111dc01281261c5451204f95",
|
||||
"reftest"
|
||||
],
|
||||
"css/css-position/position-sticky-inflow-position-ref.html": [
|
||||
"bcce2ded8073a7b5b3477bcf90157cb0e77c2b40",
|
||||
"support"
|
||||
],
|
||||
"css/css-position/position-sticky-inflow-position.html": [
|
||||
"c8e2bcdddf9e8ee93f9306d88b96c3bf1f1bfaf6",
|
||||
"reftest"
|
||||
"a0fec7d91b7261987e1f2fa5efca966d6f37bc1e",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-inline-ref.html": [
|
||||
"9458cab53d2065e4893d127ee0097bbd53c6b898",
|
||||
|
@ -501820,13 +501786,9 @@
|
|||
"2a04672cdac818a6887eac7d6824ea85d3d0559d",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-margins-ref.html": [
|
||||
"0cdb788c913f47a121114ac5b8e6a140bb08c1ff",
|
||||
"support"
|
||||
],
|
||||
"css/css-position/position-sticky-margins.html": [
|
||||
"72fb6ae7d97bf2448ebd68ccf110edd6bae2c92f",
|
||||
"reftest"
|
||||
"3f6bc9537adf2a4d477f99866d73f42b65c26db3",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-nested-bottom-ref.html": [
|
||||
"59a8e46358a8a5bf8638a2d1982c63becef5bc77",
|
||||
|
@ -501884,13 +501846,9 @@
|
|||
"a25b64d016644c272ea92b6129a59eefb21d2fa0",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-overflow-padding-ref.html": [
|
||||
"b3d81934cc90e70dff6bc5cd7789594a8fcd7ecf",
|
||||
"support"
|
||||
],
|
||||
"css/css-position/position-sticky-overflow-padding.html": [
|
||||
"588502dc7eb4a7f88f78dd1b2cdc857861c89f77",
|
||||
"reftest"
|
||||
"4a1e1c29bb47027e74437acdc1cfe073ea774f0f",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-parsing.html": [
|
||||
"224bc984bc6eb4a55931461cf7e51f7b04d219f4",
|
||||
|
@ -501908,13 +501866,9 @@
|
|||
"80caf6fb1e6c84dbf3e371a11166ac5b71bba687",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-root-scroller-ref.html": [
|
||||
"b66947a9f1b39c6c489267477d0122eeaeac7341",
|
||||
"support"
|
||||
],
|
||||
"css/css-position/position-sticky-root-scroller.html": [
|
||||
"8f77892b5a205a392942649476be7d5d54a91788",
|
||||
"reftest"
|
||||
"b7adc454ecf8b4950c46d99db11cf669b1a5d695",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-stacking-context-ref.html": [
|
||||
"dd6e5d4734c924c1ad08d14db986fb89d7cb03f6",
|
||||
|
@ -502000,21 +501954,13 @@
|
|||
"bfd49209889fc14cae5af8d7c5e7990fbde451ec",
|
||||
"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": [
|
||||
"71bdb184c1ad2d1405f683e05a5b4117c8c7362a",
|
||||
"reftest"
|
||||
"3fe5eb51028f4036f287601e3861dbd1377c588f",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-transforms.html": [
|
||||
"c3d2c2b167bcf6b8e7c45b90d9a797a216c27632",
|
||||
"reftest"
|
||||
"1b273a96f9f47c4bbbee71f41a2ceb046f7e9425",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-position/position-sticky-writing-modes-ref.html": [
|
||||
"407a1831479ccca61f6f7b268abcbf97f667f0bf",
|
||||
|
@ -519041,7 +518987,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html": [
|
||||
"460bdf5cc3e35360eb8d910274cef440837f0eaf",
|
||||
"23845856ad2da4422dc081dd8f4cc1a5d6aecfe3",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/computed/get-invalid.html": [
|
||||
|
@ -519056,10 +519002,6 @@
|
|||
"2c9c39367ccf02f014b8bf62057f810a70ff58bf",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/computed/getProperties.tentative.html": [
|
||||
"9e0018aed64fbca86d25f5ec466f88212ebd4906",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/computed/has.tentative.html": [
|
||||
"b7b16ab44745e235883303b8c495aa1ceb874d0c",
|
||||
"testharness"
|
||||
|
@ -519077,7 +519019,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html": [
|
||||
"7a6bbc1b04f6d1ee82332ddbf58a27604645a86b",
|
||||
"9ea176173ce999d17f26a80fc05b608532e471e0",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/declared/delete-invalid.html": [
|
||||
|
@ -519100,10 +519042,6 @@
|
|||
"df90a13e37ebcdbcf046e66f7e90ad2fc7edd9df",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/declared/getProperties.tentative.html": [
|
||||
"7cb65bf76de24fe33a48178f5193bc05c5ec3f44",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/declared/has.tentative.html": [
|
||||
"3299b5537f2d535988f2f6dac65b3eaba63833b2",
|
||||
"testharness"
|
||||
|
@ -519144,10 +519082,6 @@
|
|||
"2a8c50a83cbfd1c3da5614daac7070df0f5c83ff",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/inline/getProperties.tentative.html": [
|
||||
"5187e1eb4fb23b6b9a7fba8130d2649c4ea9152e",
|
||||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/inline/has.tentative.html": [
|
||||
"89b6d05db8717f1bc1f82690706d849a57104ec7",
|
||||
"testharness"
|
||||
|
@ -519209,7 +519143,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js": [
|
||||
"3ac46f9655cf07f63761850d38eb0c0cebdd98bf",
|
||||
"8f23e63849f184553cdc43d2c45aec316300db0a",
|
||||
"support"
|
||||
],
|
||||
"css/css-typed-om/the-stylepropertymap/properties/right.html": [
|
||||
|
@ -543641,7 +543575,7 @@
|
|||
"support"
|
||||
],
|
||||
"domparsing/XMLSerializer-serializeToString.html": [
|
||||
"71314752b8552c19b0951647594b9c5c85ca01b6",
|
||||
"0ff2295477d3d2d690b19976eceae8a8a79720fe",
|
||||
"testharness"
|
||||
],
|
||||
"domparsing/createContextualFragment.html": [
|
||||
|
@ -546673,7 +546607,7 @@
|
|||
"support"
|
||||
],
|
||||
"feature-policy/picture-in-picture-default-feature-policy.https.sub.html": [
|
||||
"3d493f9c8c024c4f97a5f0ae3d12977f8719bcc6",
|
||||
"49c95204e8d3478f6c4509740fa0ed605146ca36",
|
||||
"testharness"
|
||||
],
|
||||
"feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html": [
|
||||
|
@ -547629,7 +547563,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"fetch/corb/README.md": [
|
||||
"5dd841770382cd2f6f1a09dca1103ef146bc912a",
|
||||
"95e1cbd716945e9cc53297243af4cec1ac35493e",
|
||||
"support"
|
||||
],
|
||||
"fetch/corb/img-html-correctly-labeled.sub-expected.html": [
|
||||
|
@ -547732,10 +547666,18 @@
|
|||
"41e260e7df49e0e4ddb1fc5df11913dbda15edd7",
|
||||
"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": [
|
||||
"71cb97517821177aa1c1d116f830cd2315963d18",
|
||||
"testharness"
|
||||
],
|
||||
"fetch/corb/script-html-via-cross-origin-blob-url.sub.html": [
|
||||
"5fd4e36f6ad89ea7e7ede4c1a54136d80b126dd4",
|
||||
"testharness"
|
||||
],
|
||||
"fetch/corb/script-js-mislabeled-as-html-nosniff.sub.html": [
|
||||
"ff421a4a827db6c8eeec97d2a7ee7010fd8fd686",
|
||||
"testharness"
|
||||
|
@ -559400,6 +559342,10 @@
|
|||
"33bf062c202724a1ca7cd6db052614317acba1f8",
|
||||
"manual"
|
||||
],
|
||||
"html/semantics/embedded-content/media-elements/autoplay-with-broken-track.html": [
|
||||
"98f06519aeb619c9cf1db9ab3d94e987dffa182a",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/embedded-content/media-elements/contains.json": [
|
||||
"c4e395b396f7f8d9aac3e687d34c2df47dde5589",
|
||||
"support"
|
||||
|
@ -588277,7 +588223,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"service-workers/service-worker/embed-and-object-are-not-intercepted.https.html": [
|
||||
"a9f08518e369c81036ce0815aab3f1a6895fb6ab",
|
||||
"04b303491f89d1ec808548008aaa0b0747ab8672",
|
||||
"testharness"
|
||||
],
|
||||
"service-workers/service-worker/extendable-event-async-waituntil.https.html": [
|
||||
|
@ -588977,7 +588923,7 @@
|
|||
"support"
|
||||
],
|
||||
"service-workers/service-worker/resources/echo-content.py": [
|
||||
"d22a9fe2b426436a8e4901d82ca68833eeb905b3",
|
||||
"5a681fb53d70cf71d8d9ba13348025bee019ee1d",
|
||||
"support"
|
||||
],
|
||||
"service-workers/service-worker/resources/echo-message-to-source-worker.js": [
|
||||
|
@ -588985,11 +588931,15 @@
|
|||
"support"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"service-workers/service-worker/resources/embed-is-not-intercepted-iframe.html": [
|
||||
"e33a11c8d4a2a14693f8eb5eb5feb8218c92de6f",
|
||||
"e810456ed08cfa3be1730291b28b0f8134c093ed",
|
||||
"support"
|
||||
],
|
||||
"service-workers/service-worker/resources/embedded-content-from-server.html": [
|
||||
|
@ -589444,6 +589394,10 @@
|
|||
"ec72a4c120ccfac3a165576f59a0e02b945343b3",
|
||||
"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": [
|
||||
"412591c247a38b07cde7f42248bc412a50128b54",
|
||||
"support"
|
||||
|
@ -597697,11 +597651,11 @@
|
|||
"support"
|
||||
],
|
||||
"webdriver/tests/element_click/bubbling.py": [
|
||||
"5437f5ca12b93e9a0087e9b341d63af6bace4ffd",
|
||||
"dde7ca3d7b8ee65222af0044f07087d3b0725bb9",
|
||||
"wdspec"
|
||||
],
|
||||
"webdriver/tests/element_click/select.py": [
|
||||
"bbce720456bd6bae72ad256b56b7743be85959f3",
|
||||
"bddc341a0feb0d06e75415b8f98b8e9e0c2a829d",
|
||||
"wdspec"
|
||||
],
|
||||
"webdriver/tests/element_click/stale.py": [
|
||||
|
@ -597761,7 +597715,7 @@
|
|||
"support"
|
||||
],
|
||||
"webdriver/tests/execute_script/cyclic.py": [
|
||||
"cbebfbd2413ea0b10f547ab66fcc7159898e684a",
|
||||
"9d8a28b94b8cdac88650b675cb00bf21261444e8",
|
||||
"wdspec"
|
||||
],
|
||||
"webdriver/tests/execute_script/user_prompts.py": [
|
||||
|
@ -597769,7 +597723,7 @@
|
|||
"wdspec"
|
||||
],
|
||||
"webdriver/tests/fullscreen_window.py": [
|
||||
"817011a8cdff7cfd7e445fb8ecb84e5d91f03993",
|
||||
"67cc7d992d81a176297038f5516c8a9c95018040",
|
||||
"wdspec"
|
||||
],
|
||||
"webdriver/tests/get_window_rect.py": [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[display-012.xht]
|
||||
type: reftest
|
||||
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) {
|
||||
const properties = styleMap.getProperties().sort();
|
||||
const properties = [...styleMap.keys()].sort();
|
||||
var serializedStrings = [];
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const value = styleMap.get(properties[i]);
|
||||
|
|
|
@ -29,7 +29,7 @@ registerPaint('geometry', class {
|
|||
];
|
||||
}
|
||||
paint(ctx, geom, styleMap) {
|
||||
const properties = styleMap.getProperties().sort();
|
||||
const properties = [...styleMap.keys()].sort();
|
||||
var serializedStrings = [];
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const value = styleMap.get(properties[i]);
|
||||
|
|
|
@ -32,7 +32,7 @@ registerPaint('geometry', class {
|
|||
];
|
||||
}
|
||||
paint(ctx, geom, styleMap) {
|
||||
const properties = styleMap.getProperties().sort();
|
||||
const properties = [...styleMap.keys()].sort();
|
||||
var serializedStrings = [];
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const value = styleMap.get(properties[i]);
|
||||
|
|
|
@ -28,7 +28,7 @@ registerPaint('geometry', class {
|
|||
];
|
||||
}
|
||||
paint(ctx, geom, styleMap) {
|
||||
const properties = styleMap.getProperties().sort();
|
||||
const properties = [...styleMap.keys()].sort();
|
||||
var serializedStrings = [];
|
||||
for (let i = 0; i < properties.length; 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>
|
||||
<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" />
|
||||
<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>
|
||||
.scroller {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
width: 100px;
|
||||
overflow-y: scroll;
|
||||
margin-bottom: 15px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
#sticky {
|
||||
background-color: green;
|
||||
position: sticky;
|
||||
top: 150px;
|
||||
}
|
||||
|
||||
.box {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.before {
|
||||
#before {
|
||||
background-color: fuchsia;
|
||||
}
|
||||
|
||||
.after {
|
||||
#after {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.box {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.padding {
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="scroller">
|
||||
<div class="before box"></div>
|
||||
<div class="sticky box"></div>
|
||||
<div class="after box"></div>
|
||||
<div id="before" class="box"></div>
|
||||
<div id="sticky" class="box"></div>
|
||||
<div id="after" class="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>
|
||||
<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>
|
||||
<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" />
|
||||
<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>
|
||||
.group {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 400px;
|
||||
}
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
|
||||
.scroller {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 300px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
<script src="resources/sticky-util.js"></script>
|
||||
|
||||
.holder {
|
||||
width: 130px;
|
||||
height: 200px;
|
||||
}
|
||||
<body></body>
|
||||
|
||||
.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>
|
||||
window.addEventListener('load', function() {
|
||||
document.getElementById('scroller1').scrollTop = 0;
|
||||
document.getElementById('scroller2').scrollTop = 60;
|
||||
document.getElementById('scroller3').scrollTop = 120;
|
||||
});
|
||||
test(() => {
|
||||
const elements = setupStickyTest('top', 50);
|
||||
elements.sticky.style.margin = '15px';
|
||||
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>
|
||||
|
||||
<!-- 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>
|
||||
<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" />
|
||||
<meta name="assert" content="This test checks that position:sticky elements respect padding on their ancestor overflow element" />
|
||||
|
||||
<style>
|
||||
.group {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 250px;
|
||||
}
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
|
||||
.scroller {
|
||||
/* 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;
|
||||
}
|
||||
<script src="resources/sticky-util.js"></script>
|
||||
|
||||
.contents {
|
||||
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>
|
||||
<body></body>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
document.getElementById('scroller1').scrollTop = 50;
|
||||
document.getElementById('scroller2').scrollTop = 175;
|
||||
document.getElementById('scroller3').scrollTop = 220;
|
||||
});
|
||||
test(() => {
|
||||
const elements = setupStickyTest('top', 50);
|
||||
elements.scroller.style.padding = '20px 0';
|
||||
|
||||
// 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>
|
||||
|
||||
<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>
|
||||
<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" />
|
||||
<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>
|
||||
body {
|
||||
/* Assumption: 3000px is taller than any user agents test window size. */
|
||||
height: 3000px;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
background-color: red;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
background-color: green;
|
||||
#sticky {
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-color: green;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="sticky"></div>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
test(() => {
|
||||
window.scrollTo(0, 700);
|
||||
});
|
||||
assert_equals(sticky.offsetTop, 700 + 50);
|
||||
}, 'Sticky elements work with the root (document) scroller');
|
||||
</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>
|
||||
<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" />
|
||||
<meta name="assert" content="This test checks that translations on position:sticky elements are carried out on their stuck position" />
|
||||
|
||||
<style>
|
||||
.group {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 250px;
|
||||
}
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
|
||||
.scroller {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 200px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
<script src="resources/sticky-util.js"></script>
|
||||
|
||||
.contents {
|
||||
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>
|
||||
<body style="margin: 0;"></body>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
document.getElementById('scroller1').scrollTop = 50;
|
||||
document.getElementById('scroller2').scrollTop = 70;
|
||||
document.getElementById('scroller3').scrollTop = 50;
|
||||
});
|
||||
test(() => {
|
||||
const elements = setupStickyTest('top', 50);
|
||||
elements.sticky.style.transform = 'translateY(-100%)';
|
||||
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>
|
||||
|
||||
<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>
|
||||
<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" />
|
||||
<meta name="assert" content="This test checks that transforms on position:sticky elements are carried out on their stuck position" />
|
||||
|
||||
<style>
|
||||
.group {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 250px;
|
||||
}
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
|
||||
.scroller {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 200px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
<script src="resources/sticky-util.js"></script>
|
||||
|
||||
.contents {
|
||||
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>
|
||||
<body style="margin: 0;"></body>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
document.getElementById('scroller1').scrollTop = 50;
|
||||
document.getElementById('scroller2').scrollTop = 50;
|
||||
document.getElementById('scroller3').scrollTop = 50;
|
||||
});
|
||||
test(() => {
|
||||
const elements = setupStickyTest('top', 50);
|
||||
elements.sticky.style.transform = 'scale(2)';
|
||||
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>
|
||||
|
||||
<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 styleMap = target.computedStyleMap();
|
||||
|
||||
// FIXME(crbug.com/788904): Test currently fails because the 'content' property returns
|
||||
// incorrect initial CSS value.
|
||||
test(() => {
|
||||
const computedStyle = [...getComputedStyle(target)].sort();
|
||||
const properties = styleMap.getProperties();
|
||||
const properties = [...styleMap.keys()];
|
||||
|
||||
// Two extra entries for custom properties
|
||||
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;
|
||||
|
||||
test(() => {
|
||||
const properties = styleMap.getProperties();
|
||||
const properties = [...styleMap.keys()];
|
||||
assert_array_equals(properties, ['height', 'transition-duration', 'width', '--foo']);
|
||||
}, '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);
|
||||
}
|
||||
|
||||
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 = {
|
||||
'<length>': {
|
||||
description: 'a length',
|
||||
|
@ -149,26 +164,28 @@ function testPropertyValid(propertyName, examples, specified, computed, descript
|
|||
|
||||
// specified style
|
||||
const specifiedResult = element.attributeStyleMap.get(propertyName);
|
||||
if (specified || example.defaultSpecified) {
|
||||
(specified || example.defaultSpecified)(example.input, specifiedResult);
|
||||
} else {
|
||||
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) {
|
||||
(specified || example.defaultSpecified)(example.input, specifiedResult);
|
||||
} else {
|
||||
assert_style_value_equals(specifiedResult, example.input,
|
||||
`Setting ${example.description} and getting its specified value`);
|
||||
}
|
||||
|
||||
// computed style
|
||||
const computedResult = element.computedStyleMap().get(propertyName);
|
||||
if (computed || example.defaultComputed) {
|
||||
(computed || example.defaultComputed)(example.input, computedResult);
|
||||
} else {
|
||||
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) {
|
||||
(computed || example.defaultComputed)(example.input, computedResult);
|
||||
} else {
|
||||
assert_style_value_equals(computedResult, example.input,
|
||||
`Setting ${example.description} and getting its computed value`);
|
||||
}
|
||||
|
@ -220,6 +237,13 @@ function createKeywordExample(keyword) {
|
|||
function runPropertyTests(propertyName, testCases) {
|
||||
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) {
|
||||
// Retrieve test examples for this test case's syntax. If the syntax
|
||||
// looks like a keyword, then create an example on the fly.
|
||||
|
|
|
@ -42,10 +42,17 @@ test(function() {
|
|||
|
||||
test(function() {
|
||||
var serializer = new XMLSerializer();
|
||||
var root = createXmlDoc().documentElement;
|
||||
root.firstChild.setAttribute('attr1', 'value1\tvalue2\r\n');
|
||||
var xmlString = serializer.serializeToString(root);
|
||||
assert_equals(xmlString, '<root><child1 attr1="value1	value2
">value1</child1></root>');
|
||||
var parser = new DOMParser();
|
||||
var root = parser.parseFromString('<root />', 'text/xml').documentElement;
|
||||
root.setAttribute('attr', '\t');
|
||||
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');
|
||||
|
||||
</script>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
|
||||
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
|
||||
same_origin_src;
|
||||
const header = 'Default "picture-in-picture" feature policy ["self"]';
|
||||
const header = 'Default "picture-in-picture" feature policy [*]';
|
||||
|
||||
async_test(t => {
|
||||
isPictureInPictureAllowed().then(t.step_func_done((result) => {
|
||||
|
@ -26,8 +26,8 @@
|
|||
|
||||
async_test(t => {
|
||||
test_feature_availability('picture-in-picture', t, cross_origin_src,
|
||||
expect_feature_unavailable_default,);
|
||||
}, header + ' disallows cross-origin iframes.');
|
||||
expect_feature_available_default,);
|
||||
}, header + ' allows cross-origin iframes.');
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
# Tests related to Cross-Origin Resource Blocking (CORB).
|
||||
|
||||
### Summary
|
||||
|
||||
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)
|
||||
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
|
||||
the same time, some tests in this directory (e.g.
|
||||
`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.
|
||||
* 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,
|
||||
because all of these tests verify behavior that is important to the CORB
|
||||
algorithm.
|
||||
|
||||
### Limitations of WPT test coverage
|
||||
|
||||
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 => {
|
||||
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 => {
|
||||
let frame;
|
||||
|
@ -47,5 +47,32 @@ promise_test(t => {
|
|||
.then(result => {
|
||||
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>
|
||||
|
|
|
@ -4,7 +4,10 @@ def main(request, response):
|
|||
|
||||
headers = [("X-Request-Method", request.method),
|
||||
("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
|
||||
|
||||
|
|
|
@ -4,5 +4,11 @@
|
|||
self.addEventListener('fetch', e => {
|
||||
if (e.request.url.indexOf('embedded-content-from-server.html') != -1) {
|
||||
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>
|
||||
</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, "html5lib"))
|
||||
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", "funcsigs"))
|
||||
sys.path.insert(0, os.path.join(here, "third_party", "pluggy"))
|
||||
|
|
|
@ -91,6 +91,8 @@ class Manifest(object):
|
|||
hash_changed = True
|
||||
else:
|
||||
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:
|
||||
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})]
|
||||
|
||||
|
||||
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():
|
||||
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
|
||||
// until the sum of sizes reaches this threshold.
|
||||
minTotal: getIntFromInput('mintotal'),
|
||||
multipliers: getIntArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata')
|
||||
multipliers: getFloatArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata'),
|
||||
addToLog: addToLog,
|
||||
addToSummary: addToSummary,
|
||||
measureValue: measureValue,
|
||||
notifyAbort: notifyAbort
|
||||
};
|
||||
}
|
||||
|
||||
var worker = new Worker('benchmark.js');
|
||||
worker.onmessage = onMessage;
|
||||
|
||||
function onSendBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'sendBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
sendBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||
}
|
||||
|
||||
function onReceiveBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'receiveBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
receiveBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||
}
|
||||
|
||||
function onBatchBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'batchBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
batchBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'stop', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
stop(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||
}
|
||||
|
||||
function init() {
|
||||
addressBox = document.getElementById('address');
|
||||
logBox = document.getElementById('log');
|
||||
|
@ -128,6 +98,8 @@ function init() {
|
|||
if (!('WebSocket' in window)) {
|
||||
addToLog('WebSocket is not available');
|
||||
}
|
||||
|
||||
initWorker('WebSocket', '');
|
||||
}
|
||||
</script>
|
||||
</head>
|
|
@ -32,7 +32,7 @@ function destroyAllSockets() {
|
|||
sockets = [];
|
||||
}
|
||||
|
||||
function sendBenchmarkStep(size, config) {
|
||||
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
var totalSize = 0;
|
||||
|
@ -41,6 +41,7 @@ function sendBenchmarkStep(size, config) {
|
|||
var onMessageHandler = function(event) {
|
||||
if (!verifyAcknowledgement(config, event.data, size)) {
|
||||
destroyAllSockets();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,8 @@ function sendBenchmarkStep(size, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
runNextTask(config);
|
||||
};
|
||||
|
@ -89,7 +91,7 @@ function sendBenchmarkStep(size, config) {
|
|||
}
|
||||
}
|
||||
|
||||
function receiveBenchmarkStep(size, config) {
|
||||
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
var totalSize = 0;
|
||||
|
@ -101,12 +103,14 @@ function receiveBenchmarkStep(size, config) {
|
|||
config.addToLog('Expected ' + size + 'B but received ' +
|
||||
bytesReceived + 'B');
|
||||
destroyAllSockets();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.verifyData && !verifyArrayBuffer(event.data, 0x61)) {
|
||||
config.addToLog('Response verification failed');
|
||||
destroyAllSockets();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -116,7 +120,8 @@ function receiveBenchmarkStep(size, config) {
|
|||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
runNextTask(config);
|
||||
};
|
||||
|
@ -153,12 +158,11 @@ function createSocket(config) {
|
|||
};
|
||||
socket.onclose = function(event) {
|
||||
config.addToLog('Closed');
|
||||
config.notifyAbort();
|
||||
};
|
||||
return socket;
|
||||
}
|
||||
|
||||
var tasks = [];
|
||||
|
||||
function startBenchmark(config) {
|
||||
clearTimeout(timerID);
|
||||
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) {
|
||||
return '(WebSocket' +
|
||||
', ' + (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) {
|
||||
config.addToLog('Batch benchmark');
|
||||
config.addToLog(buildLegendString(config));
|
||||
|
@ -286,24 +209,6 @@ function batchBenchmark(config) {
|
|||
startBenchmark(config);
|
||||
}
|
||||
|
||||
function stop(config) {
|
||||
clearTimeout(timerID);
|
||||
timerID = null;
|
||||
config.addToLog('Stopped');
|
||||
function cleanup() {
|
||||
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;
|
||||
}
|
||||
|
||||
function calculateAndLogResult(config, size, startTimeInMs, totalSize) {
|
||||
function calculateAndLogResult(config, size, startTimeInMs, totalSize,
|
||||
isWarmUp) {
|
||||
var timeSpentInMs = getTimeStamp() - startTimeInMs;
|
||||
var speed = calculateSpeedInKB(totalSize, timeSpentInMs);
|
||||
var timePerMessageInMs = timeSpentInMs / (totalSize / size);
|
||||
if (!isWarmUp) {
|
||||
config.measureValue(timePerMessageInMs);
|
||||
if (!results[size]) {
|
||||
results[size] = {n: 0, sum_t: 0, sum_t2: 0};
|
||||
}
|
||||
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.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) {
|
||||
var i;
|
||||
|
||||
|
@ -175,3 +198,130 @@ function cloneConfig(obj) {
|
|||
}
|
||||
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) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return parseInt(document.getElementById(id).value);
|
||||
}
|
||||
|
@ -53,11 +59,7 @@ function getIntArrayFromInput(id) {
|
|||
return strArray.map(function(str) { return parseInt(str, 10); });
|
||||
}
|
||||
|
||||
function onMessage(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);
|
||||
function getFloatArrayFromInput(id) {
|
||||
var strArray = document.getElementById(id).value.split(',');
|
||||
return strArray.map(parseFloat);
|
||||
}
|
|
@ -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'),
|
||||
// Stops benchmark when the size of message exceeds this threshold.
|
||||
stopThreshold: getIntFromInput('stopthreshold'),
|
||||
// If the size of each message is small, send/receive multiple messages
|
||||
// until the sum of sizes reaches this threshold.
|
||||
// minTotal: getIntFromInput('mintotal'),
|
||||
// minTotal is not yet implemented on XHR benchmark
|
||||
multipliers: getIntArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata')
|
||||
multipliers: getFloatArrayFromInput('multipliers'),
|
||||
verifyData: getBoolFromCheckBox('verifydata'),
|
||||
methodAndCache: getStringFromRadioBox('methodandcache'),
|
||||
addToLog: addToLog,
|
||||
addToSummary: addToSummary,
|
||||
measureValue: measureValue,
|
||||
notifyAbort: notifyAbort
|
||||
};
|
||||
}
|
||||
|
||||
var worker = new Worker('xhr_benchmark.js');
|
||||
worker.onmessage = onMessage;
|
||||
|
||||
function onSendBenchmark() {
|
||||
var config = getConfig();
|
||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'sendBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
sendBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'sendBenchmark');
|
||||
}
|
||||
|
||||
function onReceiveBenchmark() {
|
||||
var config = getConfig();
|
||||
config.dataType = getStringFromRadioBox('datatyperadio');
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'receiveBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
receiveBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'receiveBenchmark');
|
||||
}
|
||||
|
||||
function onBatchBenchmark() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'batchBenchmark', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
batchBenchmark(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'batchBenchmark');
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
var config = getConfig();
|
||||
|
||||
if (getBoolFromCheckBox('worker')) {
|
||||
worker.postMessage({type: 'stop', config: config});
|
||||
} else {
|
||||
config.addToLog = addToLog;
|
||||
config.addToSummary = addToSummary;
|
||||
config.measureValue = measureValue;
|
||||
stop(config);
|
||||
}
|
||||
doAction(config, getBoolFromCheckBox('worker'), 'stop');
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
@ -127,6 +93,8 @@ function init() {
|
|||
|
||||
addToLog(window.navigator.userAgent.toLowerCase());
|
||||
addToSummary(window.navigator.userAgent.toLowerCase());
|
||||
|
||||
initWorker('XHR', '');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
@ -178,10 +146,6 @@ function init() {
|
|||
<td>Stop threshold</td>
|
||||
<td><input type="text" id="stopthreshold" value="102400000"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minimum total</td>
|
||||
<td><input type="text" id="mintotal" value="102400000"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multipliers</td>
|
||||
<td><input type="text" id="multipliers" value="5, 2"></td>
|
||||
|
@ -204,6 +168,26 @@ function init() {
|
|||
id="datatyperadioarraybuffer"
|
||||
value="arraybuffer"
|
||||
><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>
|
||||
|
||||
<div id="log_div">
|
||||
|
@ -216,7 +200,13 @@ function init() {
|
|||
id="summary" rows="20" style="width: 100%" readonly></textarea>
|
||||
</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>
|
||||
</html>
|
|
@ -33,27 +33,7 @@ function destroyAllXHRs() {
|
|||
// gc() might be needed for Chrome/Blob
|
||||
}
|
||||
|
||||
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 sendBenchmarkStep(size, config) {
|
||||
function sendBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
benchmark.startTimeInMs = null;
|
||||
|
@ -68,12 +48,14 @@ function sendBenchmarkStep(size, config) {
|
|||
if (this.status != 200) {
|
||||
config.addToLog('Failed (status=' + this.status + ')');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.verifyData &&
|
||||
!verifyAcknowledgement(config, this.response, size)) {
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,10 +68,17 @@ function sendBenchmarkStep(size, config) {
|
|||
if (benchmark.startTimeInMs == null) {
|
||||
config.addToLog('startTimeInMs not set');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
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();
|
||||
|
||||
|
@ -134,7 +123,7 @@ function sendBenchmarkStep(size, config) {
|
|||
}
|
||||
}
|
||||
|
||||
function receiveBenchmarkStep(size, config) {
|
||||
function receiveBenchmarkStep(size, config, isWarmUp) {
|
||||
timerID = null;
|
||||
|
||||
benchmark.startTimeInMs = null;
|
||||
|
@ -145,6 +134,7 @@ function receiveBenchmarkStep(size, config) {
|
|||
if (!verificationResult) {
|
||||
config.addToLog('Response verification failed');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -157,10 +147,12 @@ function receiveBenchmarkStep(size, config) {
|
|||
if (benchmark.startTimeInMs == null) {
|
||||
config.addToLog('startTimeInMs not set');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize);
|
||||
calculateAndLogResult(config, size, benchmark.startTimeInMs, totalSize,
|
||||
isWarmUp);
|
||||
|
||||
destroyAllXHRs();
|
||||
|
||||
|
@ -175,9 +167,15 @@ function receiveBenchmarkStep(size, config) {
|
|||
if (this.status != 200) {
|
||||
config.addToLog('Failed (status=' + this.status + ')');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check and warn if proxy is enabled.
|
||||
if (this.getResponseHeader('Via') !== null) {
|
||||
config.addToLog('WARNING: proxy seems enabled.');
|
||||
}
|
||||
|
||||
var bytesReceived = -1;
|
||||
if (this.responseType == 'arraybuffer') {
|
||||
bytesReceived = this.response.byteLength;
|
||||
|
@ -190,6 +188,7 @@ function receiveBenchmarkStep(size, config) {
|
|||
config.addToLog('Expected ' + size +
|
||||
'B but received ' + bytesReceived + 'B');
|
||||
destroyAllXHRs();
|
||||
config.notifyAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -220,10 +219,22 @@ function receiveBenchmarkStep(size, config) {
|
|||
|
||||
for (var i = 0; i < xhrs.length; ++i) {
|
||||
var xhr = xhrs[i];
|
||||
if (config.methodAndCache === 'GET-NOCACHE') {
|
||||
xhr.open('GET', config.prefixUrl + '_receive_getnocache?' + size,
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
originalConfig.addToLog('Batch benchmark');
|
||||
|
||||
|
@ -365,25 +288,5 @@ function batchBenchmark(originalConfig) {
|
|||
startBenchmark(config);
|
||||
}
|
||||
|
||||
|
||||
function stop(config) {
|
||||
destroyAllXHRs();
|
||||
clearTimeout(timerID);
|
||||
timerID = null;
|
||||
config.addToLog('Stopped');
|
||||
function cleanup() {
|
||||
}
|
||||
|
||||
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>
|
||||
var events = [];
|
||||
var startTime = 0;
|
||||
|
||||
function run() {
|
||||
events = [];
|
||||
startTime = Date.now();
|
||||
|
||||
function pushToLog(type) {
|
||||
var time = Date.now();
|
||||
if (events.length != 0 && type === events[events.length - 1].type) {
|
||||
events[events.length - 1].count += 1;
|
||||
events[events.length - 1].last = time;
|
||||
} 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 = '';
|
||||
for (var i = 0; i < events.length; ++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;
|
||||
}
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<textarea id="log" rows="10" cols="40" readonly></textarea>
|
||||
<textarea id="log" rows="10" cols="70" readonly></textarea>
|
||||
<br/>
|
||||
Size: <input type="text" id="size" value="65536"><br/>
|
||||
<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='',
|
||||
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:
|
||||
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')
|
||||
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 reason is not None and len(reason) > 0:
|
||||
raise BadOperationException(
|
|
@ -102,10 +102,8 @@ SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
|
|||
|
||||
# Extensions
|
||||
DEFLATE_FRAME_EXTENSION = 'deflate-frame'
|
||||
PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
|
||||
PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
|
||||
X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
|
||||
X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress'
|
||||
MUX_EXTENSION = 'mux_DO_NOT_USE'
|
||||
|
||||
# Status codes
|
||||
|
@ -153,9 +151,8 @@ def is_control_opcode(opcode):
|
|||
|
||||
|
||||
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):
|
||||
self._name = name
|
||||
|
@ -166,30 +163,39 @@ class ExtensionParameter(object):
|
|||
self._parameters = []
|
||||
|
||||
def name(self):
|
||||
"""Return the extension name."""
|
||||
return self._name
|
||||
|
||||
def add_parameter(self, name, value):
|
||||
"""Add a parameter."""
|
||||
self._parameters.append((name, value))
|
||||
|
||||
def get_parameters(self):
|
||||
"""Return the parameters."""
|
||||
return self._parameters
|
||||
|
||||
def get_parameter_names(self):
|
||||
"""Return the names of the parameters."""
|
||||
return [name for name, unused_value in self._parameters]
|
||||
|
||||
def has_parameter(self, name):
|
||||
"""Test if a parameter exists."""
|
||||
for param_name, param_value in self._parameters:
|
||||
if param_name == name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_parameter_value(self, name):
|
||||
"""Get the value of a specific parameter."""
|
||||
for param_name, param_value in self._parameters:
|
||||
if param_name == name:
|
||||
return param_value
|
||||
|
||||
|
||||
class ExtensionParsingException(Exception):
|
||||
|
||||
"""Exception to handle errors in extension parsing."""
|
||||
|
||||
def __init__(self, name):
|
||||
super(ExtensionParsingException, self).__init__(name)
|
||||
|
||||
|
@ -244,12 +250,11 @@ def _parse_extension(state):
|
|||
|
||||
|
||||
def parse_extensions(data):
|
||||
"""Parses Sec-WebSocket-Extensions header value returns a list of
|
||||
ExtensionParameter objects.
|
||||
"""Parse Sec-WebSocket-Extensions header value.
|
||||
|
||||
Returns a list of ExtensionParameter objects.
|
||||
Leading LWSes must be trimmed.
|
||||
"""
|
||||
|
||||
state = http_header_util.ParsingState(data)
|
||||
|
||||
extension_list = []
|
||||
|
@ -279,8 +284,7 @@ def parse_extensions(data):
|
|||
|
||||
|
||||
def format_extension(extension):
|
||||
"""Formats an ExtensionParameter object."""
|
||||
|
||||
"""Format an ExtensionParameter object."""
|
||||
formatted_params = [extension.name()]
|
||||
for param_name, param_value in extension.get_parameters():
|
||||
if param_value is None:
|
||||
|
@ -292,8 +296,7 @@ def format_extension(extension):
|
|||
|
||||
|
||||
def format_extensions(extension_list):
|
||||
"""Formats a list of ExtensionParameter objects."""
|
||||
|
||||
"""Format a list of ExtensionParameter objects."""
|
||||
formatted_extension_list = []
|
||||
for extension in extension_list:
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""permessage-deflate extension processor. It's also used for
|
||||
permessage-compress extension when the deflate method is chosen.
|
||||
"""permessage-deflate extension processor.
|
||||
|
||||
Specification:
|
||||
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_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
|
||||
|
||||
def __init__(self, request, draft08=True):
|
||||
"""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.
|
||||
"""
|
||||
def __init__(self, request):
|
||||
"""Construct PerMessageDeflateExtensionProcessor."""
|
||||
|
||||
ExtensionProcessorInterface.__init__(self, request)
|
||||
self._logger = util.get_class_logger(self)
|
||||
|
@ -450,22 +348,18 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
|
|||
self._preferred_client_max_window_bits = None
|
||||
self._client_no_context_takeover = False
|
||||
|
||||
self._draft08 = draft08
|
||||
|
||||
def name(self):
|
||||
# This method returns "deflate" (not "permessage-deflate") for
|
||||
# compatibility.
|
||||
return 'deflate'
|
||||
|
||||
def _get_extension_response_internal(self):
|
||||
if self._draft08:
|
||||
for name in self._request.get_parameter_names():
|
||||
if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
|
||||
self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM]:
|
||||
self._logger.debug('Unknown parameter: %r', name)
|
||||
return None
|
||||
else:
|
||||
# Any unknown parameter will be just ignored.
|
||||
pass
|
||||
|
||||
server_max_window_bits = None
|
||||
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.
|
||||
client_client_max_window_bits = self._request.has_parameter(
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
||||
if (self._draft08 and
|
||||
client_client_max_window_bits and
|
||||
if (client_client_max_window_bits and
|
||||
self._request.get_parameter_value(
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
|
||||
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)
|
||||
|
||||
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 '
|
||||
'the client cannot accept it',
|
||||
self._CLIENT_MAX_WINDOW_BITS_PARAM)
|
||||
|
@ -765,33 +658,6 @@ _available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
|
|||
_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):
|
||||
"""WebSocket multiplexing extension processor."""
|
||||
|
||||
|
@ -825,10 +691,9 @@ class MuxExtensionProcessor(ExtensionProcessorInterface):
|
|||
else:
|
||||
# Mux extension should not be applied before any history-based
|
||||
# compression extension.
|
||||
if (name == common.DEFLATE_FRAME_EXTENSION or
|
||||
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION or
|
||||
name == common.PERMESSAGE_COMPRESSION_EXTENSION or
|
||||
name == common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION):
|
||||
if (name == 'deflate' or
|
||||
name == common.DEFLATE_FRAME_EXTENSION or
|
||||
name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
|
||||
self.set_active(False)
|
||||
return
|
||||
|
|
@ -72,6 +72,7 @@ _PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True}
|
|||
|
||||
|
||||
class ApacheLogHandler(logging.Handler):
|
||||
|
||||
"""Wrapper logging.Handler to emit log message to apache's error.log."""
|
||||
|
||||
_LEVELS = {
|
||||
|
@ -136,6 +137,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def _parse_option(name, value, definition):
|
||||
"""Return the meaning of a option value."""
|
||||
if value is None:
|
||||
return False
|
||||
|
||||
|
@ -147,6 +149,7 @@ def _parse_option(name, value, definition):
|
|||
|
||||
|
||||
def _create_dispatcher():
|
||||
"""Initialize a dispatch.Dispatcher."""
|
||||
_LOGGER.info('Initializing Dispatcher')
|
||||
|
||||
options = apache.main_server.get_options()
|
||||
|
@ -187,7 +190,6 @@ def headerparserhandler(request):
|
|||
This function is named headerparserhandler because it is the default
|
||||
name for a PythonHeaderParserHandler.
|
||||
"""
|
||||
|
||||
handshake_is_done = False
|
||||
try:
|
||||
# Fallback to default http handler for request paths for which
|
||||
|
@ -234,7 +236,8 @@ def headerparserhandler(request):
|
|||
request._dispatcher = _dispatcher
|
||||
_dispatcher.transfer_data(request)
|
||||
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:
|
||||
# DispatchException can also be thrown if something is wrong in
|
||||
# pywebsocket code. It's caught here, then.
|
|
@ -40,6 +40,7 @@ import sys
|
|||
|
||||
|
||||
class MemorizingFile(object):
|
||||
|
||||
"""MemorizingFile wraps a file and memorizes lines read by readline.
|
||||
|
||||
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.
|
||||
Default: sys.maxint.
|
||||
"""
|
||||
|
||||
self._file = file_
|
||||
self._memorized_lines = []
|
||||
self._max_memorized_lines = max_memorized_lines
|
||||
|
@ -64,6 +64,11 @@ class MemorizingFile(object):
|
|||
self._buffered_line = None
|
||||
|
||||
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',
|
||||
'_buffered', '_buffered_line', 'readline',
|
||||
'get_memorized_lines'):
|
||||
|
@ -77,7 +82,6 @@ class MemorizingFile(object):
|
|||
the whole line will be read out from underlying file object by
|
||||
subsequent readline calls.
|
||||
"""
|
||||
|
||||
if self._buffered:
|
||||
line = self._buffered_line
|
||||
self._buffered = False
|
|
@ -104,23 +104,31 @@ _DROP_CODE_BAD_FRAGMENTATION = 3009
|
|||
|
||||
|
||||
class MuxUnexpectedException(Exception):
|
||||
|
||||
"""Exception in handling multiplexing extension."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# Temporary
|
||||
class MuxNotImplementedException(Exception):
|
||||
|
||||
"""Raised when a flow enters unimplemented code path."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class LogicalConnectionClosedException(Exception):
|
||||
|
||||
"""Raised when logical connection is gracefully closed."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PhysicalConnectionError(Exception):
|
||||
|
||||
"""Raised when there is a physical connection error."""
|
||||
|
||||
def __init__(self, drop_code, message=''):
|
||||
super(PhysicalConnectionError, self).__init__(
|
||||
'code=%d, message=%r' % (drop_code, message))
|
||||
|
@ -129,8 +137,11 @@ class PhysicalConnectionError(Exception):
|
|||
|
||||
|
||||
class LogicalChannelError(Exception):
|
||||
|
||||
"""Raised when there is a logical channel error."""
|
||||
|
||||
def __init__(self, channel_id, drop_code, message=''):
|
||||
"""Initialize the error with a status message."""
|
||||
super(LogicalChannelError, self).__init__(
|
||||
'channel_id=%d, code=%d, message=%r' % (
|
||||
channel_id, drop_code, message))
|
||||
|
@ -232,7 +243,9 @@ def _parse_request_text(request_text):
|
|||
|
||||
|
||||
class _ControlBlock(object):
|
||||
|
||||
"""A structure that holds parsing result of multiplexing control block.
|
||||
|
||||
Control block specific attributes will be added by _MuxFramePayloadParser.
|
||||
(e.g. encoded_handshake will be added for AddChannelRequest and
|
||||
AddChannelResponse)
|
||||
|
@ -243,6 +256,7 @@ class _ControlBlock(object):
|
|||
|
||||
|
||||
class _MuxFramePayloadParser(object):
|
||||
|
||||
"""A class that parses multiplexed frame payload."""
|
||||
|
||||
def __init__(self, payload):
|
||||
|
@ -251,13 +265,12 @@ class _MuxFramePayloadParser(object):
|
|||
self._logger = util.get_class_logger(self)
|
||||
|
||||
def read_channel_id(self):
|
||||
"""Reads channel id.
|
||||
"""Read channel id.
|
||||
|
||||
Raises:
|
||||
ValueError: when the payload doesn't contain
|
||||
valid channel id.
|
||||
"""
|
||||
|
||||
remaining_length = len(self._data) - self._read_position
|
||||
pos = self._read_position
|
||||
if remaining_length == 0:
|
||||
|
@ -288,12 +301,11 @@ class _MuxFramePayloadParser(object):
|
|||
return channel_id
|
||||
|
||||
def read_inner_frame(self):
|
||||
"""Reads an inner frame.
|
||||
"""Read an inner frame.
|
||||
|
||||
Raises:
|
||||
PhysicalConnectionError: when the inner frame is invalid.
|
||||
"""
|
||||
|
||||
if len(self._data) == self._read_position:
|
||||
raise PhysicalConnectionError(
|
||||
_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
|
||||
|
@ -345,12 +357,11 @@ class _MuxFramePayloadParser(object):
|
|||
return number
|
||||
|
||||
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
|
||||
of the WebSocket Protocol with 1 bit padding at the head.
|
||||
- the contents.
|
||||
"""
|
||||
|
||||
try:
|
||||
size = self._read_number()
|
||||
except ValueError, e:
|
||||
|
@ -455,14 +466,13 @@ class _MuxFramePayloadParser(object):
|
|||
return control_block
|
||||
|
||||
def read_control_blocks(self):
|
||||
"""Reads control block(s).
|
||||
"""Read control block(s).
|
||||
|
||||
Raises:
|
||||
PhysicalConnectionError: when the payload contains invalid control
|
||||
block(s).
|
||||
StopIteration: when no control blocks left.
|
||||
"""
|
||||
|
||||
while self._read_position < len(self._data):
|
||||
first_byte = ord(self._data[self._read_position])
|
||||
self._read_position += 1
|
||||
|
@ -488,17 +498,17 @@ class _MuxFramePayloadParser(object):
|
|||
raise StopIteration
|
||||
|
||||
def remaining_data(self):
|
||||
"""Returns remaining data."""
|
||||
|
||||
"""Return remaining data."""
|
||||
return self._data[self._read_position:]
|
||||
|
||||
|
||||
class _LogicalRequest(object):
|
||||
|
||||
"""Mimics mod_python request."""
|
||||
|
||||
def __init__(self, channel_id, command, path, protocol, headers,
|
||||
connection):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
channel_id: the channel id of the logical channel.
|
||||
|
@ -507,7 +517,6 @@ class _LogicalRequest(object):
|
|||
headers: HTTP headers.
|
||||
connection: _LogicalConnection instance.
|
||||
"""
|
||||
|
||||
self.channel_id = channel_id
|
||||
self.method = command
|
||||
self.uri = path
|
||||
|
@ -518,14 +527,16 @@ class _LogicalRequest(object):
|
|||
self.client_terminated = False
|
||||
|
||||
def is_https(self):
|
||||
"""Mimics request.is_https(). Returns False because this method is
|
||||
used only by old protocols (hixie and hybi00).
|
||||
"""
|
||||
"""Mimic request.is_https().
|
||||
|
||||
Returns False because this method is used only by old protocols
|
||||
(hixie and hybi00).
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
class _LogicalConnection(object):
|
||||
|
||||
"""Mimics mod_python mp_conn."""
|
||||
|
||||
# For details, see the comment of set_read_state().
|
||||
|
@ -534,13 +545,12 @@ class _LogicalConnection(object):
|
|||
STATE_TERMINATED = 3
|
||||
|
||||
def __init__(self, mux_handler, channel_id):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
channel_id: channel id of this connection.
|
||||
"""
|
||||
|
||||
self._mux_handler = mux_handler
|
||||
self._channel_id = channel_id
|
||||
self._incoming_data = ''
|
||||
|
@ -555,25 +565,23 @@ class _LogicalConnection(object):
|
|||
|
||||
def get_local_addr(self):
|
||||
"""Getter to mimic mp_conn.local_addr."""
|
||||
|
||||
return self._mux_handler.physical_connection.get_local_addr()
|
||||
local_addr = property(get_local_addr)
|
||||
|
||||
def get_remote_addr(self):
|
||||
"""Getter to mimic mp_conn.remote_addr."""
|
||||
|
||||
return self._mux_handler.physical_connection.get_remote_addr()
|
||||
remote_addr = property(get_remote_addr)
|
||||
|
||||
def get_memorized_lines(self):
|
||||
"""Gets memorized lines. Not supported."""
|
||||
|
||||
"""Get memorized lines. Not supported."""
|
||||
raise MuxUnexpectedException('_LogicalConnection does not support '
|
||||
'get_memorized_lines')
|
||||
|
||||
def write(self, data):
|
||||
"""Writes data. mux_handler sends data asynchronously. The caller will
|
||||
be suspended until write done.
|
||||
"""Write data. mux_handler sends data asynchronously.
|
||||
|
||||
The caller will be suspended until write done.
|
||||
|
||||
Args:
|
||||
data: data to be written.
|
||||
|
@ -582,7 +590,6 @@ class _LogicalConnection(object):
|
|||
MuxUnexpectedException: when called before finishing the previous
|
||||
write.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._write_condition.acquire()
|
||||
if self._waiting_write_completion:
|
||||
|
@ -598,18 +605,18 @@ class _LogicalConnection(object):
|
|||
self._write_condition.release()
|
||||
|
||||
def write_control_data(self, data):
|
||||
"""Writes data via the control channel. Don't wait finishing write
|
||||
because this method can be called by mux dispatcher.
|
||||
"""Write data via the control channel.
|
||||
|
||||
Don't wait finishing write because this method can be called by
|
||||
mux dispatcher.
|
||||
|
||||
Args:
|
||||
data: data to be written.
|
||||
"""
|
||||
|
||||
self._mux_handler.send_control_data(data)
|
||||
|
||||
def on_write_data_done(self):
|
||||
"""Called when sending data is completed."""
|
||||
|
||||
try:
|
||||
self._write_condition.acquire()
|
||||
if not self._waiting_write_completion:
|
||||
|
@ -623,7 +630,6 @@ class _LogicalConnection(object):
|
|||
|
||||
def on_writer_done(self):
|
||||
"""Called by the mux handler when the writer thread has finished."""
|
||||
|
||||
try:
|
||||
self._write_condition.acquire()
|
||||
self._waiting_write_completion = False
|
||||
|
@ -631,23 +637,24 @@ class _LogicalConnection(object):
|
|||
finally:
|
||||
self._write_condition.release()
|
||||
|
||||
|
||||
def append_frame_data(self, frame_data):
|
||||
"""Appends incoming frame data. Called when mux_handler dispatches
|
||||
frame data to the corresponding application.
|
||||
"""Append incoming frame data.
|
||||
|
||||
Called when mux_handler dispatches frame data to the corresponding
|
||||
application.
|
||||
|
||||
Args:
|
||||
frame_data: incoming frame data.
|
||||
"""
|
||||
|
||||
self._read_condition.acquire()
|
||||
self._incoming_data += frame_data
|
||||
self._read_condition.notify()
|
||||
self._read_condition.release()
|
||||
|
||||
def read(self, length):
|
||||
"""Reads data. Blocks until enough data has arrived via physical
|
||||
connection.
|
||||
"""Read data.
|
||||
|
||||
Blocks until enough data has arrived via physical connection.
|
||||
|
||||
Args:
|
||||
length: length of data to be read.
|
||||
|
@ -657,7 +664,6 @@ class _LogicalConnection(object):
|
|||
ConnectionTerminatedException: when the physical connection has
|
||||
closed, or an error is caused on the reader thread.
|
||||
"""
|
||||
|
||||
self._read_condition.acquire()
|
||||
while (self._read_state == self.STATE_ACTIVE and
|
||||
len(self._incoming_data) < length):
|
||||
|
@ -680,9 +686,9 @@ class _LogicalConnection(object):
|
|||
return value
|
||||
|
||||
def set_read_state(self, new_state):
|
||||
"""Sets the state of this connection. Called when an event for this
|
||||
connection has occurred.
|
||||
"""Set the state of this connection.
|
||||
|
||||
Called when an event for this connection has occurred.
|
||||
Args:
|
||||
new_state: state to be set. new_state must be one of followings:
|
||||
- STATE_GRACEFULLY_CLOSED: when closing handshake for this
|
||||
|
@ -690,7 +696,6 @@ class _LogicalConnection(object):
|
|||
- STATE_TERMINATED: when the physical connection has closed or
|
||||
DropChannel of this connection has received.
|
||||
"""
|
||||
|
||||
self._read_condition.acquire()
|
||||
self._read_state = new_state
|
||||
self._read_condition.notify()
|
||||
|
@ -698,8 +703,8 @@ class _LogicalConnection(object):
|
|||
|
||||
|
||||
class _InnerMessage(object):
|
||||
"""Holds the result of _InnerMessageBuilder.build().
|
||||
"""
|
||||
|
||||
"""Hold the result of _InnerMessageBuilder.build()."""
|
||||
|
||||
def __init__(self, opcode, payload):
|
||||
self.opcode = opcode
|
||||
|
@ -707,7 +712,10 @@ class _InnerMessage(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).
|
||||
"""
|
||||
|
||||
|
@ -791,8 +799,10 @@ class _InnerMessageBuilder(object):
|
|||
return _InnerMessage(opcode, payload)
|
||||
|
||||
def build(self, frame):
|
||||
"""Build an inner message. Returns an _InnerMessage instance when
|
||||
the given frame is the last fragmented frame. Returns None otherwise.
|
||||
"""Build an inner message.
|
||||
|
||||
Returns an _InnerMessage instance when the given frame is the last
|
||||
fragmented frame. Returns None otherwise.
|
||||
|
||||
Args:
|
||||
frame: an inner frame.
|
||||
|
@ -801,17 +811,18 @@ class _InnerMessageBuilder(object):
|
|||
receiving non continuation data opcode but the fin flag of
|
||||
the previous inner frame was not set.)
|
||||
"""
|
||||
|
||||
return self._frame_handler(frame)
|
||||
|
||||
|
||||
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):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
request: _LogicalRequest instance.
|
||||
|
@ -819,7 +830,6 @@ class _LogicalStream(Stream):
|
|||
send_quota: Initial send quota.
|
||||
receive_quota: Initial receive quota.
|
||||
"""
|
||||
|
||||
# Physical stream is responsible for masking.
|
||||
stream_options.unmask_receive = False
|
||||
Stream.__init__(self, request, stream_options)
|
||||
|
@ -928,7 +938,6 @@ class _LogicalStream(Stream):
|
|||
|
||||
def replenish_send_quota(self, send_quota):
|
||||
"""Replenish send quota."""
|
||||
|
||||
try:
|
||||
self._send_condition.acquire()
|
||||
if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
|
||||
|
@ -943,8 +952,7 @@ class _LogicalStream(Stream):
|
|||
self._send_condition.release()
|
||||
|
||||
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:
|
||||
self._logger.debug('Violate quota on channel id %d: %d < %d' %
|
||||
(self._request.channel_id,
|
||||
|
@ -955,7 +963,6 @@ class _LogicalStream(Stream):
|
|||
|
||||
def send_message(self, message, end=True, binary=False):
|
||||
"""Override Stream.send_message."""
|
||||
|
||||
if self._request.server_terminated:
|
||||
raise BadOperationException(
|
||||
'Requested send_message after sending out a closing handshake')
|
||||
|
@ -985,14 +992,13 @@ class _LogicalStream(Stream):
|
|||
self._last_message_was_fragmented = not end
|
||||
|
||||
def _receive_frame(self):
|
||||
"""Overrides Stream._receive_frame.
|
||||
"""Override Stream._receive_frame.
|
||||
|
||||
In addition to call Stream._receive_frame, this method adds the amount
|
||||
of payload to receiving quota and sends FlowControl to the client.
|
||||
We need to do it here because Stream.receive_message() handles
|
||||
control frames internally.
|
||||
"""
|
||||
|
||||
opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
|
||||
amount = len(payload)
|
||||
# Replenish extra one octet when receiving the first fragmented frame.
|
||||
|
@ -1007,9 +1013,7 @@ class _LogicalStream(Stream):
|
|||
return opcode, payload, fin, rsv1, rsv2, rsv3
|
||||
|
||||
def _get_message_from_frame(self, frame):
|
||||
"""Overrides Stream._get_message_from_frame.
|
||||
"""
|
||||
|
||||
"""Override Stream._get_message_from_frame."""
|
||||
try:
|
||||
inner_message = self._inner_message_builder.build(frame)
|
||||
except InvalidFrameException:
|
||||
|
@ -1022,8 +1026,7 @@ class _LogicalStream(Stream):
|
|||
return inner_message.payload
|
||||
|
||||
def receive_message(self):
|
||||
"""Overrides Stream.receive_message."""
|
||||
|
||||
"""Override Stream.receive_message."""
|
||||
# Just call Stream.receive_message(), but catch
|
||||
# LogicalConnectionClosedException, which is raised when the logical
|
||||
# connection has closed gracefully.
|
||||
|
@ -1034,8 +1037,7 @@ class _LogicalStream(Stream):
|
|||
return None
|
||||
|
||||
def _send_closing_handshake(self, code, reason):
|
||||
"""Overrides Stream._send_closing_handshake."""
|
||||
|
||||
"""Override Stream._send_closing_handshake."""
|
||||
body = create_closing_handshake_body(code, reason)
|
||||
self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
|
||||
(self._request.channel_id, code, reason))
|
||||
|
@ -1044,8 +1046,7 @@ class _LogicalStream(Stream):
|
|||
self._request.server_terminated = True
|
||||
|
||||
def send_ping(self, body=''):
|
||||
"""Overrides Stream.send_ping"""
|
||||
|
||||
"""Override Stream.send_ping."""
|
||||
self._logger.debug('Sending ping on logical channel %d: %r' %
|
||||
(self._request.channel_id, body))
|
||||
self._write_inner_frame(common.OPCODE_PING, body, end=True)
|
||||
|
@ -1053,23 +1054,20 @@ class _LogicalStream(Stream):
|
|||
self._ping_queue.append(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._request.channel_id, body))
|
||||
self._write_inner_frame(common.OPCODE_PONG, body, end=True)
|
||||
|
||||
def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
|
||||
"""Overrides Stream.close_connection."""
|
||||
|
||||
"""Override Stream.close_connection."""
|
||||
# TODO(bashi): Implement
|
||||
self._logger.debug('Closing logical connection %d' %
|
||||
self._request.channel_id)
|
||||
self._request.server_terminated = True
|
||||
|
||||
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_closed = True
|
||||
self._send_condition.notify()
|
||||
|
@ -1077,7 +1075,10 @@ class _LogicalStream(Stream):
|
|||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
@ -1087,6 +1088,7 @@ class _OutgoingData(object):
|
|||
|
||||
|
||||
class _PhysicalConnectionWriter(threading.Thread):
|
||||
|
||||
"""A thread that is responsible for writing data to physical connection.
|
||||
|
||||
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):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._mux_handler = mux_handler
|
||||
|
@ -1118,16 +1119,14 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
self._deque_condition = threading.Condition()
|
||||
|
||||
def put_outgoing_data(self, data):
|
||||
"""Puts outgoing data.
|
||||
"""Put outgoing data.
|
||||
|
||||
Args:
|
||||
data: _OutgoingData instance.
|
||||
|
||||
Raises:
|
||||
BadOperationException: when the thread has been requested to
|
||||
terminate.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._deque_condition.acquire()
|
||||
if self._stop_requested:
|
||||
|
@ -1193,8 +1192,7 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
self._mux_handler.notify_writer_done()
|
||||
|
||||
def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
|
||||
"""Stops the writer thread."""
|
||||
|
||||
"""Stop the writer thread."""
|
||||
self._deque_condition.acquire()
|
||||
self._stop_requested = True
|
||||
self._close_code = close_code
|
||||
|
@ -1203,16 +1201,16 @@ class _PhysicalConnectionWriter(threading.Thread):
|
|||
|
||||
|
||||
class _PhysicalConnectionReader(threading.Thread):
|
||||
|
||||
"""A thread that is responsible for reading data from physical connection.
|
||||
"""
|
||||
|
||||
def __init__(self, mux_handler):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._mux_handler = mux_handler
|
||||
|
@ -1254,18 +1252,18 @@ class _PhysicalConnectionReader(threading.Thread):
|
|||
|
||||
|
||||
class _Worker(threading.Thread):
|
||||
|
||||
"""A thread that is responsible for running the corresponding application
|
||||
handler.
|
||||
"""
|
||||
|
||||
def __init__(self, mux_handler, request):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
mux_handler: _MuxHandler instance.
|
||||
request: _LogicalRequest instance.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self._logger = util.get_class_logger(self)
|
||||
self._mux_handler = mux_handler
|
||||
|
@ -1286,19 +1284,20 @@ class _Worker(threading.Thread):
|
|||
|
||||
|
||||
class _MuxHandshaker(hybi.Handshaker):
|
||||
|
||||
"""Opening handshake processor for multiplexing."""
|
||||
|
||||
_DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
|
||||
|
||||
def __init__(self, request, dispatcher, send_quota, receive_quota):
|
||||
"""Constructs an instance.
|
||||
"""Construct an instance.
|
||||
|
||||
Args:
|
||||
request: _LogicalRequest instance.
|
||||
dispatcher: Dispatcher instance (dispatch.Dispatcher).
|
||||
send_quota: Initial send quota.
|
||||
receive_quota: Initial receive quota.
|
||||
"""
|
||||
|
||||
hybi.Handshaker.__init__(self, request, dispatcher)
|
||||
self._send_quota = send_quota
|
||||
self._receive_quota = receive_quota
|
||||
|
@ -1316,7 +1315,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
def _create_stream(self, stream_options):
|
||||
"""Override hybi.Handshaker._create_stream."""
|
||||
|
||||
self._logger.debug('Creating logical stream for %d' %
|
||||
self._request.channel_id)
|
||||
return _LogicalStream(
|
||||
|
@ -1325,7 +1323,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
def _create_handshake_response(self, accept):
|
||||
"""Override hybi._create_handshake_response."""
|
||||
|
||||
response = []
|
||||
|
||||
response.append('HTTP/1.1 101 Switching Protocols\r\n')
|
||||
|
@ -1348,7 +1345,6 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
def _send_handshake(self, accept):
|
||||
"""Override hybi.Handshaker._send_handshake."""
|
||||
|
||||
# Don't send handshake response for the default channel
|
||||
if self._request.channel_id == _DEFAULT_CHANNEL_ID:
|
||||
return
|
||||
|
@ -1363,8 +1359,8 @@ class _MuxHandshaker(hybi.Handshaker):
|
|||
|
||||
|
||||
class _LogicalChannelData(object):
|
||||
"""A structure that holds information about logical channel.
|
||||
"""
|
||||
|
||||
"""A structure that holds information about logical channel."""
|
||||
|
||||
def __init__(self, request, worker):
|
||||
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