├── .gitignore
├── .travis.yml
├── README.md
├── design
├── logo-export.svg
├── logo.ai
├── logo.psd
└── logo.svg
├── gulpfile.js
├── package-lock.json
├── package.json
└── src
├── base.html
├── css
├── _components.scss
├── _global.scss
├── _layout.scss
├── _page-specific.scss
├── _utils.scss
└── all.scss
├── data.json
├── demos
├── claim
│ ├── index.html
│ └── sw.js
├── clients-count
│ ├── index.html
│ └── sw.js
├── fetch
│ ├── index.html
│ ├── json.json
│ └── sw.js
├── fetchevent
│ ├── index.html
│ └── sw.js
├── force-reload-loop
│ ├── index.html
│ └── sw.js
├── forward-requests
│ ├── index.html
│ └── sw.js
├── gif-stream
│ ├── babel.html
│ ├── buffer.html
│ ├── cat.mpg
│ ├── gif.html
│ ├── gif.js
│ ├── index.html
│ ├── jsmpeg-async.js
│ ├── jsmpeg-babel.js
│ ├── jsmpeg-stream.js
│ ├── jsmpeg.js
│ ├── regenerator-runtime.js
│ ├── stream.html
│ └── sw.js
├── globalapis
│ ├── index.html
│ └── sw.js
├── headers-log
│ ├── index.html
│ └── sw.js
├── http-redirect
│ ├── blah
│ │ └── index.html
│ ├── index.html
│ └── sw.js
├── img-rewrite
│ ├── index.html
│ └── sw.js
├── install-fail
│ ├── error-thrown-oninstall
│ │ ├── index.html
│ │ └── sw.js
│ ├── execution-error
│ │ ├── index.html
│ │ └── sw.js
│ └── rejected-promise
│ │ ├── index.html
│ │ └── sw.js
├── installactivate
│ ├── index.html
│ └── sw.js
├── json-stream
│ ├── index.html
│ ├── photos.json
│ └── photos.sjson
├── manual-response
│ ├── index.html
│ └── sw.js
├── nav-preload
│ ├── imgs.html
│ ├── index.html
│ ├── styles.css
│ └── sw.js
├── navigator.serviceWorker
│ └── index.html
├── page-cache-bug
│ ├── index.html
│ └── sw.js
├── postMessage
│ ├── index.html
│ └── sw.js
├── redirect
│ ├── destination
│ │ └── index.html
│ ├── index.html
│ └── sw.js
├── registerunregister
│ ├── index.html
│ └── sw.js
├── scope
│ ├── app-a
│ │ ├── index.html
│ │ └── sw.js
│ ├── app-b-sw.js
│ ├── app-b
│ │ └── index.html
│ ├── index.html
│ └── sw.js
├── simple-stream
│ ├── index.html
│ └── sw.js
├── slow-update
│ ├── index.html
│ └── sw.js
├── sync
│ ├── index.html
│ └── sw.js
├── template-stream
│ ├── index.html
│ ├── prism.js
│ ├── styles.css
│ └── sw.js
└── transform-stream
│ ├── cloud.html
│ ├── edge-cases.md
│ ├── index.html
│ └── sw.js
├── img
├── chrome-canary.png
├── chrome.png
├── edge.png
├── firefox-nightly.png
├── firefox.png
├── opera-developer.png
├── opera.png
├── safari.png
├── samsung-internet.png
└── webkit.png
├── index.html
├── masthead.html
└── resources.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .sass-cache
2 | build
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # For more information about the configurations used
2 | # in this file, please see the Travis CI documentation:
3 | # http://docs.travis-ci.com
4 |
5 | deploy:
6 | provider: pages
7 | skip_cleanup: true
8 | github_token: $GH_TOKEN
9 | local_dir: build
10 | on:
11 | branch: master
12 |
13 | env:
14 | global:
15 | - secure: "aFvIDrSx6T91kHM7JHJvwdU90eVZ/XIo3mztoDPHW2eysHomdwwE17ShEEQahOVTslOqjt88U7R8vcAoVliEie64oP/wK29To38Pe3N3YLvGeTfxYKuDhbUmm5U+16qTmgFTjlVbQXCMMH4HSjcPujIzX3wbd/vuF1mkj/cY8Z0="
16 |
17 | git:
18 | depth: 10
19 |
20 | language: node_js
21 |
22 | node_js:
23 | - "stable"
24 |
25 | script: npm run build
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Is Service Worker Ready Yet?
2 |
3 | [](https://travis-ci.org/jakearchibald/isserviceworkerready)
4 | [](https://david-dm.org/jakearchibald/isserviceworkerready#info=devDependencies)
5 |
6 | Tracks the features of service worker supported in browsers.
7 | [View the site](https://jakearchibald.github.io/isserviceworkerready).
8 |
9 | ## Run locally
10 |
11 | To install, run the following in the root of your cloned copy of the repo:
12 |
13 | ```sh
14 | npm install
15 | ```
16 |
17 | To serve the site on `localhost:8000`:
18 |
19 | ```sh
20 | npm run serve
21 | ```
22 |
23 | To build the site:
24 |
25 | ```sh
26 | npm run build
27 | ```
28 |
29 | ## Contribute
30 |
31 | To update data, edit [`data.json`](src/data.json), which is in this format:
32 |
33 | ```js
34 |
35 | //...
36 |
37 | "features": [
38 |
39 | //...
40 |
41 | {
42 | "name", "Feature name or interface.whatever
",
43 | "description", "Brief feature details, html allowed ",
44 | "chrome": {
45 | // 1 = supported
46 | // 0.5 = supported with caveats (eg flags, nightlies, special builds)
47 | // 0 = not supported
48 | "supported": 1
49 | // (optional) browser version
50 | "minVersion": 35,
51 | // (optional) alternate icon, currently supports:
52 | // "chrome-canary"
53 | // "firefox-nightly"
54 | // "webkit"
55 | // "opera-developer"
56 | "icon": "canary",
57 | // (optional) details, cavats, links to tickets, flags etc
58 | "details": [
59 | "Requires Chrome Canary "
60 | ]
61 | },
62 | "firefox": {},
63 | "opera": {},
64 | "safari": {},
65 | // (optional) details that don't apply to a single browser
66 | "details": [
67 | "Chrome & Firefox : sitting in a tree K-I-S-S-I-N-G"
68 | ]
69 | },
70 |
71 | // ...
72 |
73 | ]
74 | ```
75 |
--------------------------------------------------------------------------------
/design/logo-export.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/design/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakearchibald/isserviceworkerready/cda40edfbd47d023a06534e1d0e68b55a6a9f6d0/design/logo.ai
--------------------------------------------------------------------------------
/design/logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakearchibald/isserviceworkerready/cda40edfbd47d023a06534e1d0e68b55a6a9f6d0/design/logo.psd
--------------------------------------------------------------------------------
/design/logo.svg:
--------------------------------------------------------------------------------
1 |
2 | is
3 | SERVICEWORKER
4 | ready
5 | ?
6 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | var del = require('del');
4 | var browserSync = require('browser-sync');
5 | var gulp = require('gulp');
6 | var plugins = require('gulp-load-plugins')();
7 | var runSequence = require('run-sequence'); // Temporary solution until Gulp 4
8 | // https://github.com/gulpjs/gulp/issues/355
9 |
10 | var reload = browserSync.reload;
11 |
12 | // ---------------------------------------------------------------------
13 | // | Helper tasks |
14 | // ---------------------------------------------------------------------
15 |
16 | gulp.task('clean', function () {
17 | return del(['build']);
18 | });
19 |
20 | gulp.task('copy', [
21 | 'copy:css',
22 | 'copy:html',
23 | 'copy:misc'
24 | ]);
25 |
26 | gulp.task('copy:css', function () {
27 | return gulp.src('src/css/all.scss')
28 | .pipe(plugins.sass({ style: 'compressed' }))
29 | .pipe(gulp.dest('build/css'))
30 | .pipe(plugins.filter('**/*.css'))
31 | .pipe(reload({stream: true}));
32 | });
33 |
34 | gulp.task('copy:html', function () {
35 | return gulp.src([
36 |
37 | // Copy all `.html` files
38 | 'src/*.html',
39 |
40 | // Exclude the following files since they
41 | // are only used to build the other files
42 | '!src/masthead.html',
43 | '!src/base.html'
44 |
45 | ]).pipe(plugins.swig({
46 | defaults: { cache: false },
47 | data: JSON.parse(fs.readFileSync("./src/data.json"))
48 | })).pipe(plugins.htmlmin({
49 |
50 | // In-depth information about the options:
51 | // https://github.com/kangax/html-minifier#options-quick-reference
52 |
53 | collapseBooleanAttributes: true,
54 | collapseWhitespace: true,
55 | minifyJS: true,
56 | removeAttributeQuotes: true,
57 | removeComments: true,
58 | removeEmptyAttributes: true,
59 | removeOptionalTags: true,
60 | removeRedundantAttributes: true,
61 |
62 | // Prevent html-minifier from breaking the SVGs
63 | // https://github.com/kangax/html-minifier/issues/285
64 | keepClosingSlash: true,
65 | caseSensitive: true
66 |
67 | })).pipe(gulp.dest('build')).pipe(reload({stream: true}));
68 | });
69 |
70 | gulp.task('copy:misc', function () {
71 | return gulp.src([
72 |
73 | // Copy all files
74 | 'src/**',
75 |
76 | // Exclude the following files
77 | // (other tasks will handle the copying of these files)
78 | '!src/*.html',
79 | '!src/{css,css/**}',
80 | '!src/data.json'
81 |
82 | ], {
83 | // Include hidden files by default
84 | dot: true
85 | }).pipe(gulp.dest('build'));
86 |
87 | });
88 |
89 | gulp.task('browser-sync', function() {
90 | browserSync({
91 |
92 | // In-depth information about the options:
93 | // http://www.browsersync.io/docs/options/
94 |
95 | notify: false,
96 | port: 8000,
97 | server: "build",
98 | open: false
99 | });
100 | });
101 |
102 | gulp.task('watch', function () {
103 | gulp.watch(['src/**/*.scss'], ['copy:css']);
104 | gulp.watch(['src/*.html', 'src/data.json'], ['copy:html', reload]);
105 | gulp.watch(['src/img/**', 'src/demos/**'], ['copy:misc']);
106 | });
107 |
108 | // ---------------------------------------------------------------------
109 | // | Main tasks |
110 | // ---------------------------------------------------------------------
111 |
112 | gulp.task('build', function (done) {
113 | runSequence('clean', 'copy', done);
114 | });
115 |
116 | gulp.task('default', ['build']);
117 |
118 | gulp.task('serve', function (done) {
119 | runSequence('build', ['browser-sync', 'watch'], done);
120 | });
121 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "browser-sync": "^2.24.1",
4 | "del": "^3.0.0",
5 | "gulp": "^3.8.10",
6 | "gulp-filter": "^5.1.0",
7 | "gulp-htmlmin": "^4.0.0",
8 | "gulp-load-plugins": "^1.5.0",
9 | "gulp-sass": "^4.0.1",
10 | "gulp-swig": "^0.8.0",
11 | "run-sequence": "^2.2.1"
12 | },
13 | "private": true,
14 | "scripts": {
15 | "build": "gulp build",
16 | "serve": "gulp serve"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% block title %}{% endblock %}
5 |
6 |
18 |
19 |
20 |
21 | {% block head %}{% endblock %}
22 |
23 |
24 | {% block body %}{% endblock %}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/css/_components.scss:
--------------------------------------------------------------------------------
1 | .page-title {
2 | position: absolute;
3 | left: 0;
4 | top: -5000px;
5 | }
6 |
7 | .hero {
8 | height: calc(100vh - 2em - 60px);
9 | display: flex;
10 | justify-content: space-between;
11 | flex-direction: column;
12 | margin-bottom: 1em;
13 | padding-top: 1rem;
14 | @media (min-width: 500px) {
15 | padding-top: 3rem;
16 | height: calc(100vh - 3em - 2em);
17 | }
18 |
19 | @media (min-width: 800px) {
20 | padding-top: 4.5rem;
21 | height: calc(100vh - 4.5em - 2em);
22 | }
23 |
24 | @media (min-width: 720px) {
25 | padding-top: 125px;
26 | height: calc(100vh - 125px - 2em);
27 | }
28 | }
29 |
30 | .logo {
31 | max-width: 947px;
32 | fill: #FFF;
33 | display: block;
34 | margin: 0 auto 0;
35 | position: relative;
36 | padding-top: 18.98%;
37 |
38 | & svg {
39 | position: absolute;
40 | top: 0;
41 | left: 0;
42 | width: 100%;
43 | height: 100%;
44 | margin: 0 auto;
45 | }
46 | }
47 |
48 | .answer {
49 | font-family: "Press Start 2P";
50 | text-align: center;
51 | color: #fff;
52 | margin: 0 auto;
53 | display: block;
54 | font-size: 5em;
55 | text-shadow: 5px 5px 0px rgba(0, 0, 0, 0.5);
56 | }
57 |
58 | .external-links {
59 | font-family: "Press Start 2P";
60 | margin: 0.8rem 0 1.1rem;
61 | padding: 0;
62 | text-align: center;
63 | color: #fff;
64 |
65 | @media (min-width: 500px) {
66 | margin: 0.8rem 0 3.2rem;
67 | }
68 |
69 | @media (min-width: 800px) {
70 | margin: 0.8rem 0 4.4rem;
71 | }
72 |
73 | & > li {
74 | margin: 0 10px;
75 | display: inline-block;
76 |
77 | @media (min-width: 360px) {
78 | margin: 0;
79 |
80 | &::after {
81 | content: '*';
82 | margin: 0 1rem;
83 | }
84 |
85 | &:last-child::after {
86 | content: none;
87 | }
88 | }
89 | }
90 |
91 | & a:link,
92 | & a:visited {
93 | color: #71B5FF;
94 | }
95 | & a:hover,
96 | & a:active {
97 | text-decoration: underline;
98 | }
99 | }
100 |
101 | .details-link {
102 | display: block;
103 | width: 100%;
104 | color: #fff;
105 | text-align: center;
106 | svg {
107 | height: 10vh;
108 | width: 10vh;
109 | }
110 | }
111 |
112 | %box-heading {
113 | background: #2A609B;
114 | line-height: 1;
115 | padding: 0.4rem 1rem;
116 | font-weight: normal;
117 | font-size: 1rem;
118 | color: #fff;
119 | margin: 0 -1rem;
120 | overflow: hidden;
121 | text-overflow: ellipsis;
122 | }
123 |
124 | .feature {
125 | margin: 0 auto;
126 | margin-bottom: 1rem;
127 | line-height: 1.2;
128 | max-width: 510px;
129 |
130 | @media (min-width: 800px) {
131 | @include display-flex;
132 | @include flex-flow(wrap);
133 | @include justify-content(flex-end);
134 | max-width: 947px;
135 | }
136 |
137 | &:target > header {
138 | background: #FFECA9;
139 | }
140 |
141 | & > header {
142 | background: #fff;
143 | overflow: hidden;
144 | padding: 0 1rem;
145 |
146 | @media (min-width: 800px) {
147 | width: 35%;
148 | box-sizing: border-box;
149 | }
150 |
151 | & p {
152 | margin: 0.7rem 0;
153 | }
154 | }
155 |
156 | & .feature-name {
157 | @extend %box-heading;
158 | position: relative;
159 | }
160 |
161 | & .perma {
162 | &:link,
163 | &:visited {
164 | color: #fff;
165 | }
166 | &:hover,
167 | &:active {
168 | text-decoration: none;
169 | & .feature-name::after {
170 | content: '¶';
171 | position: absolute;
172 | top: 0.3rem;
173 | right: 0.3rem;
174 | color: rgba(255, 255, 255, 0.53);
175 | }
176 | }
177 | }
178 | }
179 |
180 | .feature > header.no-background {
181 | background: none;
182 | }
183 |
184 | .results {
185 | @include display-flex;
186 | background: #fff;
187 |
188 | :target & {
189 | background: #FFECA9;
190 | }
191 |
192 | @media (min-width: 800px) {
193 | width: 65%;
194 | }
195 |
196 | & .result {
197 | @include flex(1);
198 | }
199 | }
200 |
201 | .result {
202 | margin: 0 0.4rem 0.4rem;
203 | margin-left: 0;
204 | overflow: hidden;
205 | position: relative;
206 |
207 | @media (min-width: 800px) {
208 | margin-top: 0.4rem;
209 | }
210 |
211 | &:first-child {
212 | margin-left: 0.4rem;
213 | }
214 |
215 | &::after {
216 | display: block;
217 | content: '';
218 | padding-top: 100%;
219 | }
220 |
221 | &.yes {
222 | background: #376D37;
223 | }
224 |
225 | &.ish {
226 | background: #D5BB00;
227 | }
228 |
229 | &.no {
230 | background: #CCC;
231 | & .icon {
232 | opacity: 0.2;
233 | -webkit-filter: grayscale(100%);
234 |
235 | @supports (-webkit-filter: grayscale(100%)) {
236 | opacity: 0.4;
237 | }
238 | }
239 | }
240 |
241 | & .support {
242 | margin: 0;
243 | text-indent: -5000px;
244 | position: absolute;
245 | top: 0;
246 | left: 0;
247 | bottom: 0;
248 | right: 0;
249 | }
250 |
251 | & .version {
252 | position: absolute;
253 | bottom: 0;
254 | right: 0;
255 | background: rgba(255, 255, 255, 0.8);
256 | line-height: 1;
257 | color: #000;
258 | text-indent: 0;
259 | padding: 0.1rem 0.2rem;
260 | font-size: 0.8rem;
261 |
262 | &::after {
263 | content: '+';
264 | }
265 |
266 | @media (min-width: 578px) {
267 | border-radius: 4px;
268 | bottom: 5%;
269 | right: 5%;
270 | padding: 0;
271 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.3);
272 | @include display-flex;
273 | @include justify-content(center);
274 | @include align-items(center);
275 |
276 | width: 50%;
277 | height: 30%;
278 | }
279 | }
280 |
281 | & .icon {
282 | text-indent: -5000px;
283 | position: absolute;
284 | top: 0;
285 | right: 0;
286 | bottom: 0;
287 | left: 0;
288 | }
289 | }
290 |
291 | .icon {
292 | margin: 0;
293 | background-size: 78%;
294 | background-repeat: no-repeat;
295 | background-position: center center;
296 |
297 | @each $icon in chrome, chrome-canary, firefox, firefox-nightly, edge, opera, opera-developer, samsung-internet, safari, webkit {
298 | &.#{$icon} {
299 | background-image: url(../img/#{$icon}.png);
300 | }
301 | }
302 | }
303 |
304 | .details {
305 | margin: 0;
306 | padding: 0;
307 | font-size: 0.9rem;
308 |
309 | @media (min-width: 800px) {
310 | width: 65%;
311 | box-sizing: border-box;
312 | }
313 |
314 | & > li {
315 | display: block;
316 | background: #E4E4E4;
317 | margin: 0;
318 | padding: 0.4rem 1rem;
319 |
320 | &:nth-child(even) {
321 | background: #F1F1F1;
322 | }
323 |
324 | &:first-child {
325 | border-top: solid 1px #CFCFCF;
326 | }
327 |
328 | }
329 | }
330 |
331 | .outro {
332 | font-family: "Press Start 2P";
333 | margin: 2.2rem 0;
334 | text-align: center;
335 | color: #fff;
336 | }
337 |
--------------------------------------------------------------------------------
/src/css/_global.scss:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: sans-serif;
3 | font-size: 15px;
4 | line-height: 1.4;
5 | min-height: 100%;
6 | background: #102a48;
7 | background: #102a48 radial-gradient(circle closest-corner at 50% 63px, #166ab9, #013668, #102a48) no-repeat;
8 | overflow-y: scroll;
9 |
10 | -webkit-text-size-adjust: 100%;
11 |
12 | @media (min-width: 500px) {
13 | background: #102a48 radial-gradient(circle closest-corner at 50% 126px, #166ab9, #013668, #102a48) no-repeat;
14 | font-size: 17px;
15 | }
16 |
17 | @media (min-width: 720px) {
18 | background: #102a48 radial-gradient(circle closest-corner at 50% 256px, #166ab9, #013668, #102a48) no-repeat;
19 | }
20 | }
21 |
22 | body {
23 | margin: 1rem;
24 | @media (min-width: 500px) {
25 | margin: 1.8rem;
26 | }
27 | }
28 |
29 | a {
30 | &:link,
31 | &:visited {
32 | color: #1F6DC2;
33 | text-decoration: none;
34 | }
35 | &:hover,
36 | &:active {
37 | color: #007AFF;
38 | text-decoration: underline;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/css/_layout.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakearchibald/isserviceworkerready/cda40edfbd47d023a06534e1d0e68b55a6a9f6d0/src/css/_layout.scss
--------------------------------------------------------------------------------
/src/css/_page-specific.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakearchibald/isserviceworkerready/cda40edfbd47d023a06534e1d0e68b55a6a9f6d0/src/css/_page-specific.scss
--------------------------------------------------------------------------------
/src/css/_utils.scss:
--------------------------------------------------------------------------------
1 | @mixin placeholder {
2 | &::-webkit-input-placeholder,
3 | &::-moz-placeholder,
4 | &:-ms-input-placeholder {
5 | @content;
6 | }
7 | }
8 |
9 | // transition & animation
10 | $easeInQuad : cubic-bezier(0.550, 0.085, 0.680, 0.530);
11 | $easeInCubic : cubic-bezier(0.550, 0.055, 0.675, 0.190);
12 | $easeInQuart : cubic-bezier(0.895, 0.030, 0.685, 0.220);
13 | $easeInQuint : cubic-bezier(0.755, 0.050, 0.855, 0.060);
14 | $easeInSine : cubic-bezier(0.470, 0.000, 0.745, 0.715);
15 | $easeInExpo : cubic-bezier(0.950, 0.050, 0.795, 0.035);
16 | $easeInCirc : cubic-bezier(0.600, 0.040, 0.980, 0.335);
17 | $easeInBack : cubic-bezier(0.600, -0.280, 0.735, 0.045);
18 |
19 | $easeOutQuad : cubic-bezier(0.250, 0.460, 0.450, 0.940);
20 | $easeOutCubic : cubic-bezier(0.215, 0.610, 0.355, 1.000);
21 | $easeOutQuart : cubic-bezier(0.165, 0.840, 0.440, 1.000);
22 | $easeOutQuint : cubic-bezier(0.230, 1.000, 0.320, 1.000);
23 | $easeOutSine : cubic-bezier(0.390, 0.575, 0.565, 1.000);
24 | $easeOutExpo : cubic-bezier(0.190, 1.000, 0.220, 1.000);
25 | $easeOutCirc : cubic-bezier(0.075, 0.820, 0.165, 1.000);
26 | $easeOutBack : cubic-bezier(0.175, 0.885, 0.320, 1.275);
27 |
28 | $easeInOutQuad : cubic-bezier(0.455, 0.030, 0.515, 0.955);
29 | $easeInOutCubic : cubic-bezier(0.645, 0.045, 0.355, 1.000);
30 | $easeInOutQuart : cubic-bezier(0.770, 0.000, 0.175, 1.000);
31 | $easeInOutQuint : cubic-bezier(0.860, 0.000, 0.070, 1.000);
32 | $easeInOutSine : cubic-bezier(0.445, 0.050, 0.550, 0.950);
33 | $easeInOutExpo : cubic-bezier(1.000, 0.000, 0.000, 1.000);
34 | $easeInOutCirc : cubic-bezier(0.785, 0.135, 0.150, 0.860);
35 | $easeInOutBack : cubic-bezier(0.680, -0.550, 0.265, 1.550);
36 |
37 | @mixin transition($spec...) {
38 | @each $prefix in -webkit-, '' {
39 | #{$prefix}transition: $spec;
40 | }
41 | }
42 |
43 | @mixin transition-property($spec...) {
44 | @each $prefix in -webkit-, '' {
45 | #{$prefix}transition-property: $spec;
46 | }
47 | }
48 |
49 | @mixin animation($type) {
50 | @each $prefix in -webkit-, '' {
51 | #{$prefix}animation: $type;
52 | }
53 | }
54 |
55 | @mixin keyframes($name) {
56 | @-webkit-keyframes $name { @content; }
57 | @keyframes $name { @content; }
58 | }
59 |
60 | @mixin transform($spec...) {
61 | @each $prefix in -webkit-, '' {
62 | #{$prefix}transform: $spec;
63 | }
64 | }
65 |
66 | // flexbox
67 | @mixin display-flex {
68 | @each $prefix in -webkit-, '' {
69 | display: #{$prefix}flex;
70 | }
71 | }
72 | @mixin flex-direction($spec...) {
73 | @each $prefix in -webkit-, '' {
74 | #{$prefix}flex-direction: $spec;
75 | }
76 | }
77 | @mixin justify-content($spec...) {
78 | @each $prefix in -webkit-, '' {
79 | #{$prefix}justify-content: $spec;
80 | }
81 | }
82 | @mixin flex($spec...) {
83 | @each $prefix in -webkit-, '' {
84 | #{$prefix}flex: $spec;
85 | }
86 | }
87 | @mixin flex-flow($spec...) {
88 | @each $prefix in -webkit-, '' {
89 | #{$prefix}flex-flow: $spec;
90 | }
91 | }
92 | @mixin align-items($spec...) {
93 | @each $prefix in -webkit-, '' {
94 | #{$prefix}align-items: $spec;
95 | }
96 | }
97 | @mixin max-height-max-content {
98 | @each $prefix in -webkit-, -moz-, '' {
99 | max-height: #{$prefix}max-content;
100 | }
101 | }
102 | @mixin max-height-min-content {
103 | @each $prefix in -webkit-, -moz-, '' {
104 | max-height: #{$prefix}max-content;
105 | }
106 | }
107 | @mixin min-height-min-content {
108 | @each $prefix in -webkit-, -moz-, '' {
109 | min-height: #{$prefix}min-content;
110 | }
111 | }
112 | @mixin min-width-min-content {
113 | @each $prefix in -webkit-, -moz-, '' {
114 | min-width: #{$prefix}min-content;
115 | }
116 | }
--------------------------------------------------------------------------------
/src/css/all.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 | @import 'global';
3 | @import 'layout';
4 | @import 'components';
5 | @import 'page-specific';
--------------------------------------------------------------------------------
/src/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "browsers": [
3 | {
4 | "id": "chrome",
5 | "name": "Chrome"
6 | },
7 | {
8 | "id": "firefox",
9 | "name": "Firefox"
10 | },
11 | {
12 | "id": "opera",
13 | "name": "Opera"
14 | },
15 | {
16 | "id": "samsung-internet",
17 | "name": "Samsung Internet"
18 | },
19 | {
20 | "id": "safari",
21 | "name": "Safari"
22 | },
23 | {
24 | "id": "edge",
25 | "name": "Edge"
26 | }
27 | ],
28 | "features": [
29 | {
30 | "name": "Service worker enthusiasm",
31 | "description": "The first thing any implementation needs.",
32 | "chrome": {
33 | "supported": 1,
34 | "details": [
35 | "Shipped."
36 | ]
37 | },
38 | "firefox": {
39 | "supported": 1,
40 | "details": [
41 | "Shipped."
42 | ]
43 | },
44 | "opera": {
45 | "supported": 1
46 | },
47 | "samsung-internet": {
48 | "supported": 1,
49 | "details": [
50 | "Shipped. Based on Chromium 44.2403 with some additions and changes . (See \"Service Worker\" section.)"
51 | ]
52 | },
53 | "safari": {
54 | "supported": 1,
55 | "details": [
56 | "Shipped ."
57 | ]
58 | },
59 | "edge": {
60 | "supported": 1,
61 | "details": [
62 | "Shipped ."
63 | ]
64 | },
65 | "details": [
66 | "Support does not include iOS versions of third-party browsers on that platform (see Safari support)."
67 | ]
68 | },
69 | {
70 | "name": "Promises",
71 | "description": "Not service worker-specific, but required by service worker. Spec .",
72 | "chrome": {
73 | "supported": 1,
74 | "minVersion": 36
75 | },
76 | "firefox": {
77 | "supported": 1,
78 | "minVersion": 29
79 | },
80 | "opera": {
81 | "supported": 1,
82 | "minVersion": 23
83 | },
84 | "samsung-internet": {
85 | "supported": 1,
86 | "minVersion": 2.0
87 | },
88 | "safari": {
89 | "minVersion": 9,
90 | "supported": 1
91 | },
92 | "edge": {
93 | "icon": "edge",
94 | "minVersion": 13,
95 | "supported": 1
96 | }
97 | },
98 | {
99 | "name": "Debugging",
100 | "description": "State of debugging tools.",
101 | "chrome": {
102 | "supported": 1,
103 | "minVersion": 40,
104 | "details": [
105 | "You can debug service worker scripts as any other. \"Application\" panel in devtools has service worker & cache sections."
106 | ]
107 | },
108 | "firefox": {
109 | "supported": 1,
110 | "minVersion": 47,
111 | "details": [
112 | "Debuggable from the \"Workers\" page in about:debugging
.",
113 | "Web Console can display console messages from service workers.",
114 | "about:serviceworkers
has some under-the-hood stuff."
115 | ]
116 | },
117 | "opera": {
118 | "supported": 1,
119 | "details": [
120 | "Debuggable from the resources panel in Opera developer if you enable super-experimental devtools .",
121 | "Console messages from the service worker appear in the pages' console in Opera stable."
122 | ]
123 | },
124 | "samsung-internet": {
125 | "supported": 1
126 | },
127 | "safari": {
128 | "supported": 1,
129 | "minVersion": 11.1,
130 | "details": [
131 | "See 'service workers' in the 'develop' menu to open an inspector for a particular service worker."
132 | ]
133 | },
134 | "edge": {
135 | "minVersion": 17,
136 | "supported": 1,
137 | "details": [
138 | "See the service worker section in the sources panel."
139 | ]
140 | },
141 | "details": [
142 | "Chrome & Opera : Debuggable from the resources panel in Chrome Canary and Opera developer if you enable super-experimental devtools .",
143 | "Chrome & Opera : Console messages from the service worker appear in the pages' console.",
144 | "Chrome & Opera & Samsung Internet : chrome://serviceworker-internals
resp. browser://serviceworker-internals
(in Opera developer) has some under-the-hood stuff."
145 | ]
146 | },
147 | {
148 | "name": "navigator.serviceWorker
",
149 | "description": "Namespace for page-side service worker API. Spec . Test .",
150 | "chrome": {
151 | "supported": 1,
152 | "minVersion": 40
153 | },
154 | "firefox": {
155 | "supported": 1,
156 | "minVersion": 44
157 | },
158 | "opera": {
159 | "supported": 1,
160 | "minVersion": 27
161 | },
162 | "samsung-internet": {
163 | "supported": 1,
164 | "minVersion": 4.0
165 | },
166 | "safari": {
167 | "supported": 1,
168 | "minVersion": 11.1
169 | },
170 | "edge": {
171 | "supported": 1,
172 | "minVersion": 17
173 | },
174 | "details": [
175 | ]
176 | },
177 | {
178 | "name": "Register / unregister",
179 | "description": "Register for a SW and get a registration instance back, unregister
undoes. Spec . Test .",
180 | "chrome": {
181 | "supported": 1,
182 | "minVersion": 40
183 | },
184 | "firefox": {
185 | "supported": 1,
186 | "minVersion": 44
187 | },
188 | "opera": {
189 | "supported": 1,
190 | "minVersion": 27
191 | },
192 | "samsung-internet": {
193 | "supported": 1,
194 | "minVersion": 4.0
195 | },
196 | "safari": {
197 | "supported": 1,
198 | "minVersion": 11.1
199 | },
200 | "edge": {
201 | "supported": 1,
202 | "minVersion": 17
203 | },
204 | "details": [
205 | ]
206 | },
207 | {
208 | "name": "postMessage
to & from worker",
209 | "description": "Spec . Test .",
210 | "chrome": {
211 | "supported": 1,
212 | "minVersion": 45
213 | },
214 | "firefox": {
215 | "supported": 1,
216 | "minVersion": 44
217 | },
218 | "opera": {
219 | "supported": 1,
220 | "minVersion": 32
221 | },
222 | "samsung-internet": {
223 | "supported": 1,
224 | "minVersion": 4.0
225 | },
226 | "safari": {
227 | "supported": 1,
228 | "minVersion": 11.1
229 | },
230 | "edge": {
231 | "supported": 1,
232 | "minVersion": 17
233 | },
234 | "details": [
235 | ]
236 | },
237 | {
238 | "name": "Fetch event",
239 | "description": "Fires for pages and all sub-resources. Spec . Test .",
240 | "chrome": {
241 | "supported": 1,
242 | "minVersion": 40
243 | },
244 | "firefox": {
245 | "supported": 1,
246 | "minVersion": 44
247 | },
248 | "opera": {
249 | "supported": 1,
250 | "minVersion": 27
251 | },
252 | "samsung-internet": {
253 | "supported": 1,
254 | "minVersion": 4.0
255 | },
256 | "safari": {
257 | "supported": 1,
258 | "minVersion": 11.1
259 | },
260 | "edge": {
261 | "supported": 1,
262 | "minVersion": 17
263 | }
264 | },
265 | {
266 | "name": "fetchEvent.request
",
267 | "description": "Spec . Test .",
268 | "chrome": {
269 | "supported": 1,
270 | "minVersion": 40
271 | },
272 | "firefox": {
273 | "supported": 1,
274 | "minVersion": 44
275 | },
276 | "opera": {
277 | "supported": 1,
278 | "minVersion": 27
279 | },
280 | "samsung-internet": {
281 | "supported": 1,
282 | "minVersion": 4.0
283 | },
284 | "safari": {
285 | "supported": 1,
286 | "minVersion": 11.1
287 | },
288 | "edge": {
289 | "supported": 1,
290 | "minVersion": 17
291 | }
292 | },
293 | {
294 | "name": "fetchEvent.respondWith()
",
295 | "description": "Spec . Test .",
296 | "chrome": {
297 | "supported": 1,
298 | "minVersion": 40
299 | },
300 | "firefox": {
301 | "supported": 1,
302 | "minVersion": 44
303 | },
304 | "opera": {
305 | "supported": 1,
306 | "minVersion": 27
307 | },
308 | "samsung-internet": {
309 | "supported": 1,
310 | "minVersion": 4.0
311 | },
312 | "safari": {
313 | "supported": 1,
314 | "minVersion": 11.1
315 | },
316 | "edge": {
317 | "supported": 1,
318 | "minVersion": 17
319 | }
320 | },
321 | {
322 | "name": "Install event",
323 | "description": "Install event fires in a newly discovered SW. Includes InstallEvent.waitUntil()
. Spec . Test ",
324 | "chrome": {
325 | "supported": 1,
326 | "minVersion": 40
327 | },
328 | "firefox": {
329 | "supported": 1,
330 | "minVersion": 44
331 | },
332 | "opera": {
333 | "supported": 1,
334 | "minVersion": 27
335 | },
336 | "samsung-internet": {
337 | "supported": 1,
338 | "minVersion": 4.0
339 | },
340 | "safari": {
341 | "supported": 1,
342 | "minVersion": 11.1
343 | },
344 | "edge": {
345 | "supported": 1,
346 | "minVersion": 17
347 | }
348 | },
349 | {
350 | "name": "self.skipWaiting()
",
351 | "description": "Allow an installing worker to take over from the current active worker once installed. Spec . Test .",
352 | "chrome": {
353 | "supported": 1,
354 | "minVersion": 42
355 | },
356 | "firefox": {
357 | "supported": 1,
358 | "minVersion": 44
359 | },
360 | "opera": {
361 | "supported": 1,
362 | "minVersion": 27
363 | },
364 | "samsung-internet": {
365 | "supported": 1,
366 | "minVersion": 4.0
367 | },
368 | "safari": {
369 | "supported": 1,
370 | "minVersion": 11.1
371 | },
372 | "edge": {
373 | "supported": 1,
374 | "minVersion": 17
375 | }
376 | },
377 | {
378 | "name": "Activate event",
379 | "description": "Activate event fires once this worker becomes the active worker in a registration. Includes event.waitUntil()
. Spec . Test .",
380 | "chrome": {
381 | "supported": 1,
382 | "minVersion": 40
383 | },
384 | "firefox": {
385 | "supported": 1,
386 | "minVersion": 44
387 | },
388 | "opera": {
389 | "supported": 1,
390 | "minVersion": 27
391 | },
392 | "samsung-internet": {
393 | "supported": 1,
394 | "minVersion": 4.0
395 | },
396 | "safari": {
397 | "supported": 1,
398 | "minVersion": 11.1
399 | },
400 | "edge": {
401 | "supported": 1,
402 | "minVersion": 17
403 | }
404 | },
405 | {
406 | "name": "clients.claim()
",
407 | "description": "Allow an active worker to take control of pages in its scope (eg, documents that were loaded before the SW was registered). Spec . Test .",
408 | "chrome": {
409 | "supported": 1,
410 | "minVersion": 42
411 | },
412 | "firefox": {
413 | "supported": 1,
414 | "minVersion": 44
415 | },
416 | "opera": {
417 | "supported": 1,
418 | "minVersion": 33
419 | },
420 | "samsung-internet": {
421 | "supported": 1,
422 | "minVersion": 4.0
423 | },
424 | "safari": {
425 | "supported": 1,
426 | "minVersion": 11.1
427 | },
428 | "edge": {
429 | "supported": 1,
430 | "minVersion": 17
431 | }
432 | },
433 | {
434 | "name": "Update checks",
435 | "description": "Browser checks for SW updates after navigation. Spec .",
436 | "chrome": {
437 | "supported": 1,
438 | "minVersion": 40
439 | },
440 | "firefox": {
441 | "supported": 1,
442 | "minVersion": 44
443 | },
444 | "opera": {
445 | "supported": 1,
446 | "minVersion": 27
447 | },
448 | "samsung-internet": {
449 | "supported": 1,
450 | "minVersion": 4.0
451 | },
452 | "safari": {
453 | "supported": 1,
454 | "minVersion": 11.1
455 | },
456 | "edge": {
457 | "supported": 1,
458 | "minVersion": 17
459 | }
460 | },
461 | {
462 | "name": "Service worker lifecycle",
463 | "description": "Allow a next version to be in waiting & take over when appropriate.",
464 | "chrome": {
465 | "supported": 1,
466 | "minVersion": 40
467 | },
468 | "firefox": {
469 | "supported": 1,
470 | "minVersion": 44
471 | },
472 | "opera": {
473 | "supported": 1,
474 | "minVersion": 27
475 | },
476 | "samsung-internet": {
477 | "supported": 1,
478 | "minVersion": 4.0
479 | },
480 | "safari": {
481 | "supported": 1,
482 | "minVersion": 11.1
483 | },
484 | "edge": {
485 | "supported": 1,
486 | "minVersion": 17
487 | }
488 | },
489 | {
490 | "name": "Request
",
491 | "description": "Spec . Test .",
492 | "chrome": {
493 | "supported": 1,
494 | "minVersion": 40
495 | },
496 | "firefox": {
497 | "supported": 1,
498 | "minVersion": 39
499 | },
500 | "opera": {
501 | "supported": 1,
502 | "minVersion": 27
503 | },
504 | "samsung-internet": {
505 | "supported": 1,
506 | "minVersion": 4.0
507 | },
508 | "safari": {
509 | "supported": 1,
510 | "minVersion": 10.1
511 | },
512 | "edge": {
513 | "supported": 1,
514 | "minVersion": 14
515 | }
516 | },
517 | {
518 | "name": "Response
",
519 | "description": "Spec . Test .",
520 | "chrome": {
521 | "supported": 1,
522 | "minVersion": 40
523 | },
524 | "firefox": {
525 | "supported": 1,
526 | "minVersion": 39
527 | },
528 | "opera": {
529 | "supported": 1,
530 | "minVersion": 33
531 | },
532 | "samsung-internet": {
533 | "supported": 1,
534 | "minVersion": 4.0
535 | },
536 | "safari": {
537 | "supported": 1,
538 | "minVersion": 10.1
539 | },
540 | "edge": {
541 | "supported": 1,
542 | "minVersion": 14
543 | },
544 | "details": [
545 | "Chrome & Samsung Internet : URLSearchParams
not supported yet "
546 | ]
547 | },
548 | {
549 | "name": "fetch(request)
",
550 | "description": "Spec . Test .",
551 | "chrome": {
552 | "supported": 1,
553 | "minVersion": 40
554 | },
555 | "firefox": {
556 | "supported": 1,
557 | "minVersion": 39
558 | },
559 | "opera": {
560 | "supported": 1,
561 | "minVersion": 27
562 | },
563 | "samsung-internet": {
564 | "supported": 1,
565 | "minVersion": 4.0
566 | },
567 | "safari": {
568 | "supported": 1,
569 | "minVersion": 10.1
570 | },
571 | "edge": {
572 | "supported": 1,
573 | "minVersion": 14
574 | },
575 | "details": [
576 | "Polyfill available "
577 | ]
578 | },
579 | {
580 | "name": "caches
",
581 | "description": "Spec . Test .",
582 | "chrome": {
583 | "supported": 1,
584 | "minVersion": 46
585 | },
586 | "firefox": {
587 | "supported": 1,
588 | "minVersion": 44
589 | },
590 | "opera": {
591 | "supported": 1,
592 | "minVersion": 33
593 | },
594 | "samsung-internet": {
595 | "supported": 1,
596 | "minVersion": 4.0
597 | },
598 | "safari": {
599 | "supported": 1,
600 | "minVersion": 11.1
601 | },
602 | "edge": {
603 | "supported": 1,
604 | "minVersion": 16
605 | }
606 | },
607 | {
608 | "name": "serviceWorker.ready
",
609 | "description": "Spec . Test .",
610 | "chrome": {
611 | "supported": 1
612 | },
613 | "firefox": {
614 | "supported": 1,
615 | "minVersion": 44
616 | },
617 | "opera": {
618 | "supported": 1,
619 | "minVersion": 33
620 | },
621 | "samsung-internet": {
622 | "supported": 1,
623 | "minVersion": 4.0
624 | },
625 | "safari": {
626 | "supported": 1,
627 | "minVersion": 11.1
628 | },
629 | "edge": {
630 | "supported": 1,
631 | "minVersion": 17
632 | }
633 | },
634 | {
635 | "name": "Background sync",
636 | "description": "Deferring tasks until the user has connectivity. Spec . Test .",
637 | "chrome": {
638 | "supported": 1,
639 | "minVersion": 49
640 | },
641 | "firefox": {
642 | "supported": 0,
643 | "details": [
644 | "Bug 1217544 "
645 | ]
646 | },
647 | "opera": {
648 | "supported": 0
649 | },
650 | "samsung-internet": {
651 | "supported": 0
652 | },
653 | "safari": {
654 | "supported": 0
655 | },
656 | "edge": {
657 | "supported": 0,
658 | "details": [
659 | "In development "
660 | ]
661 | }
662 | }
663 | ]
664 | }
665 |
--------------------------------------------------------------------------------
/src/demos/claim/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 | Perform fetch
13 |
59 |
60 |
--------------------------------------------------------------------------------
/src/demos/claim/sw.js:
--------------------------------------------------------------------------------
1 | self.onactivate = function() {
2 | clients.claim();
3 | };
4 |
5 | self.onmessage = function(event) {
6 | if (event.data == 'claim') {
7 | clients.claim();
8 | }
9 | };
10 |
11 |
12 | self.onfetch = function(event) {
13 | var url = new URL(event.request.url);
14 | if (url.pathname.endsWith('/404.json')) {
15 | event.respondWith(
16 | new Response('{"This came from": "The ServiceWorker"}', {
17 | headers: {
18 | "Content-Type": "application/json"
19 | }
20 | })
21 | );
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/src/demos/clients-count/index.html:
--------------------------------------------------------------------------------
1 |
2 | No controller, refresh to load via SW
3 |
--------------------------------------------------------------------------------
/src/demos/clients-count/sw.js:
--------------------------------------------------------------------------------
1 | self.skipWaiting();
2 |
3 | self.addEventListener('fetch', event => {
4 | event.respondWith(
5 | clients.matchAll().then(clients => new Response(`Number of controlled clients = ${clients.length}`))
6 | );
7 | });
--------------------------------------------------------------------------------
/src/demos/fetch/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
32 |
33 |
--------------------------------------------------------------------------------
/src/demos/fetch/json.json:
--------------------------------------------------------------------------------
1 | {"hello": "world"}
2 |
--------------------------------------------------------------------------------
/src/demos/fetch/sw.js:
--------------------------------------------------------------------------------
1 | console.log("fetch", this.fetch);
2 |
3 | if (this.fetch) {
4 | console.log("Attempting fetch");
5 | fetch('./').then(function(res) {
6 | console.log("Response", res);
7 | return res.text();
8 | }).then(function(text) {
9 | console.log("body", text);
10 | }).catch(function(err) {
11 | console.error(err);
12 | }).then(function() {
13 | console.log("Attempting JSON fetch");
14 | return fetch('./json.json');
15 | }).then(function(res) {
16 | console.log("Response", res);
17 | return res.json();
18 | }).then(function(data) {
19 | console.log("body", data);
20 | }).catch(function(err) {
21 | console.error(err);
22 | }).then(function() {
23 | console.log("Attempting fetch outside of scope");
24 | return fetch('/');
25 | }).then(function(res) {
26 | console.log("Response", res);
27 | return res.text();
28 | }).then(function(text) {
29 | console.log("body", text);
30 | }).catch(function(err) {
31 | console.error(err);
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/src/demos/fetchevent/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/src/demos/fetchevent/sw.js:
--------------------------------------------------------------------------------
1 | console.log("SW startup");
2 |
3 | this.onfetch = function(event) {
4 | console.log("Fetch event", event);
5 | console.log(".request", event.request);
6 | console.log(".respondWith", event.respondWith);
7 | console.log(".default", event.default);
8 |
9 | if (event.respondWith) {
10 | event.respondWith(new Response(new Blob(["Hello world "], {type : 'text/html'}), {
11 | headers: {"Content-Type": "text/html"}
12 | }));
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/demos/force-reload-loop/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 | This page refreshes once the controller changes
14 |
15 |
16 |
17 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/demos/force-reload-loop/sw.js:
--------------------------------------------------------------------------------
1 | // Blah
--------------------------------------------------------------------------------
/src/demos/forward-requests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 | \o/
14 |
15 |
16 |
17 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/demos/forward-requests/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('activate', _ => {
2 | clients.claim();
3 | });
4 |
5 | self.addEventListener('fetch', event => {
6 | console.log(event.request);
7 | event.respondWith(fetch(event.request));
8 | });
--------------------------------------------------------------------------------
/src/demos/gif-stream/babel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 | Decoding MPEG
16 |
17 |
18 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/demos/gif-stream/buffer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 | Decoding MPEG
16 |
17 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/demos/gif-stream/cat.mpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakearchibald/isserviceworkerready/cda40edfbd47d023a06534e1d0e68b55a6a9f6d0/src/demos/gif-stream/cat.mpg
--------------------------------------------------------------------------------
/src/demos/gif-stream/gif.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Streaming GIF demo
5 |
6 |
14 |
15 |
16 | Streaming gif
17 | The following gif is dynamically generated from an MPEG1 video.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/demos/gif-stream/gif.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o {
70 | this.readControllers.push(controller);
71 | }
72 | });
73 | }
74 |
75 | /*
76 | GIFEncoder.prototype.createWriteStream = function (options) {
77 | var self = this;
78 | if (options) {
79 | Object.keys(options).forEach(function (option) {
80 | var fn = 'set' + option[0].toUpperCase() + option.substr(1);
81 | if (~['setDelay', 'setFrameRate', 'setDispose', 'setRepeat',
82 | 'setTransparent', 'setQuality'].indexOf(fn)) {
83 | self[fn].call(self, options[option]);
84 | }
85 | });
86 | }
87 |
88 | var ws = new stream.Duplex({ objectMode: true });
89 | ws._read = function () {};
90 | this.createReadStream(ws);
91 |
92 | var self = this;
93 | ws._write = function (data, enc, next) {
94 | if (!self.started) self.start();
95 | self.addFrame(data);
96 | next();
97 | };
98 | var end = ws.end;
99 | ws.end = function () {
100 | end.apply(ws, [].slice.call(arguments));
101 | self.finish();
102 | };
103 | return ws;
104 | };
105 | */
106 |
107 | GIFEncoder.prototype.emit = function() {
108 | if (this.readControllers.length === 0) return;
109 | if (this.out.data.length) {
110 | this.readControllers.forEach(c => c.enqueue(Uint8Array.from(this.out.data)));
111 | this.out.data = [];
112 | }
113 | };
114 |
115 | GIFEncoder.prototype.end = function() {
116 | if (this.readControllers.length === null) return;
117 | this.emit();
118 | this.readControllers.forEach(c => c.close());
119 | this.readControllers = [];
120 | };
121 |
122 | /*
123 | Sets the delay time between each frame, or changes it for subsequent frames
124 | (applies to the next frame added)
125 | */
126 | GIFEncoder.prototype.setDelay = function(milliseconds) {
127 | this.delay = Math.round(milliseconds / 10);
128 | };
129 |
130 | /*
131 | Sets frame rate in frames per second.
132 | */
133 | GIFEncoder.prototype.setFrameRate = function(fps) {
134 | this.delay = Math.round(100 / fps);
135 | };
136 |
137 | /*
138 | Sets the GIF frame disposal code for the last added frame and any
139 | subsequent frames.
140 |
141 | Default is 0 if no transparent color has been set, otherwise 2.
142 | */
143 | GIFEncoder.prototype.setDispose = function(disposalCode) {
144 | if (disposalCode >= 0) this.dispose = disposalCode;
145 | };
146 |
147 | /*
148 | Sets the number of times the set of GIF frames should be played.
149 |
150 | -1 = play once
151 | 0 = repeat indefinitely
152 |
153 | Default is -1
154 |
155 | Must be invoked before the first image is added
156 | */
157 |
158 | GIFEncoder.prototype.setRepeat = function(repeat) {
159 | this.repeat = repeat;
160 | };
161 |
162 | /*
163 | Sets the transparent color for the last added frame and any subsequent
164 | frames. Since all colors are subject to modification in the quantization
165 | process, the color in the final palette for each frame closest to the given
166 | color becomes the transparent color for that frame. May be set to null to
167 | indicate no transparent color.
168 | */
169 | GIFEncoder.prototype.setTransparent = function(color) {
170 | this.transparent = color;
171 | };
172 |
173 | /*
174 | Adds next GIF frame. The frame is not written immediately, but is
175 | actually deferred until the next frame is received so that timing
176 | data can be inserted. Invoking finish() flushes all frames.
177 | */
178 | GIFEncoder.prototype.addFrame = function(imageData) {
179 | // HTML Canvas 2D Context Passed In
180 | if (imageData && imageData.getImageData) {
181 | this.image = imageData.getImageData(0, 0, this.width, this.height).data;
182 | } else {
183 | this.image = imageData;
184 | }
185 |
186 | this.getImagePixels(); // convert to correct format if necessary
187 | this.analyzePixels(); // build color table & map pixels
188 |
189 | if (this.firstFrame) {
190 | this.writeLSD(); // logical screen descriptior
191 | this.writePalette(); // global color table
192 | if (this.repeat >= 0) {
193 | // use NS app extension to indicate reps
194 | this.writeNetscapeExt();
195 | }
196 | }
197 |
198 | this.writeGraphicCtrlExt(); // write graphic control extension
199 | this.writeImageDesc(); // image descriptor
200 | if (!this.firstFrame) this.writePalette(); // local color table
201 | this.writePixels(); // encode and write pixel data
202 |
203 | this.firstFrame = false;
204 | this.emit();
205 | };
206 |
207 | /*
208 | Adds final trailer to the GIF stream, if you don't call the finish method
209 | the GIF stream will not be valid.
210 | */
211 | GIFEncoder.prototype.finish = function() {
212 | this.out.writeByte(0x3b); // gif trailer
213 | this.end();
214 | };
215 |
216 | /*
217 | Sets quality of color quantization (conversion of images to the maximum 256
218 | colors allowed by the GIF specification). Lower values (minimum = 1)
219 | produce better colors, but slow processing significantly. 10 is the
220 | default, and produces good color mapping at reasonable speeds. Values
221 | greater than 20 do not yield significant improvements in speed.
222 | */
223 | GIFEncoder.prototype.setQuality = function(quality) {
224 | if (quality < 1) quality = 1;
225 | this.sample = quality;
226 | };
227 |
228 | /*
229 | Writes GIF file header
230 | */
231 | GIFEncoder.prototype.start = function() {
232 | this.out.writeUTFBytes("GIF89a");
233 | this.started = true;
234 | this.emit();
235 | };
236 |
237 | /*
238 | Analyzes current frame colors and creates color map.
239 | */
240 | GIFEncoder.prototype.analyzePixels = function() {
241 | var len = this.pixels.length;
242 | var nPix = len / 3;
243 |
244 | this.indexedPixels = new Uint8Array(nPix);
245 |
246 | var imgq = new NeuQuant(this.pixels, this.sample);
247 | imgq.buildColormap(); // create reduced palette
248 | this.colorTab = imgq.getColormap();
249 |
250 | // map image pixels to new palette
251 | var k = 0;
252 | for (var j = 0; j < nPix; j++) {
253 | var index = imgq.lookupRGB(
254 | this.pixels[k++] & 0xff,
255 | this.pixels[k++] & 0xff,
256 | this.pixels[k++] & 0xff
257 | );
258 | this.usedEntry[index] = true;
259 | this.indexedPixels[j] = index;
260 | }
261 |
262 | this.pixels = null;
263 | this.colorDepth = 8;
264 | this.palSize = 7;
265 |
266 | // get closest match to transparent color if specified
267 | if (this.transparent !== null) {
268 | this.transIndex = this.findClosest(this.transparent);
269 | }
270 | };
271 |
272 | /*
273 | Returns index of palette color closest to c
274 | */
275 | GIFEncoder.prototype.findClosest = function(c) {
276 | if (this.colorTab === null) return -1;
277 |
278 | var r = (c & 0xFF0000) >> 16;
279 | var g = (c & 0x00FF00) >> 8;
280 | var b = (c & 0x0000FF);
281 | var minpos = 0;
282 | var dmin = 256 * 256 * 256;
283 | var len = this.colorTab.length;
284 |
285 | for (var i = 0; i < len;) {
286 | var dr = r - (this.colorTab[i++] & 0xff);
287 | var dg = g - (this.colorTab[i++] & 0xff);
288 | var db = b - (this.colorTab[i] & 0xff);
289 | var d = dr * dr + dg * dg + db * db;
290 | var index = i / 3;
291 | if (this.usedEntry[index] && (d < dmin)) {
292 | dmin = d;
293 | minpos = index;
294 | }
295 | i++;
296 | }
297 |
298 | return minpos;
299 | };
300 |
301 | /*
302 | Extracts image pixels into byte array pixels
303 | (removes alphachannel from canvas imagedata)
304 | */
305 | GIFEncoder.prototype.getImagePixels = function() {
306 | var w = this.width;
307 | var h = this.height;
308 | this.pixels = new Uint8Array(w * h * 3);
309 |
310 | var data = this.image;
311 | var count = 0;
312 |
313 | for (var i = 0; i < h; i++) {
314 | for (var j = 0; j < w; j++) {
315 | var b = (i * w * 4) + j * 4;
316 | this.pixels[count++] = data[b];
317 | this.pixels[count++] = data[b+1];
318 | this.pixels[count++] = data[b+2];
319 | }
320 | }
321 | };
322 |
323 | /*
324 | Writes Graphic Control Extension
325 | */
326 | GIFEncoder.prototype.writeGraphicCtrlExt = function() {
327 | this.out.writeByte(0x21); // extension introducer
328 | this.out.writeByte(0xf9); // GCE label
329 | this.out.writeByte(4); // data block size
330 |
331 | var transp, disp;
332 | if (this.transparent === null) {
333 | transp = 0;
334 | disp = 0; // dispose = no action
335 | } else {
336 | transp = 1;
337 | disp = 2; // force clear if using transparent color
338 | }
339 |
340 | if (this.dispose >= 0) {
341 | disp = this.dispose & 7; // user override
342 | }
343 | disp <<= 2;
344 |
345 | // packed fields
346 | this.out.writeByte(
347 | 0 | // 1:3 reserved
348 | disp | // 4:6 disposal
349 | 0 | // 7 user input - 0 = none
350 | transp // 8 transparency flag
351 | );
352 |
353 | this.writeShort(this.delay); // delay x 1/100 sec
354 | this.out.writeByte(this.transIndex); // transparent color index
355 | this.out.writeByte(0); // block terminator
356 | };
357 |
358 | /*
359 | Writes Image Descriptor
360 | */
361 | GIFEncoder.prototype.writeImageDesc = function() {
362 | this.out.writeByte(0x2c); // image separator
363 | this.writeShort(0); // image position x,y = 0,0
364 | this.writeShort(0);
365 | this.writeShort(this.width); // image size
366 | this.writeShort(this.height);
367 |
368 | // packed fields
369 | if (this.firstFrame) {
370 | // no LCT - GCT is used for first (or only) frame
371 | this.out.writeByte(0);
372 | } else {
373 | // specify normal LCT
374 | this.out.writeByte(
375 | 0x80 | // 1 local color table 1=yes
376 | 0 | // 2 interlace - 0=no
377 | 0 | // 3 sorted - 0=no
378 | 0 | // 4-5 reserved
379 | this.palSize // 6-8 size of color table
380 | );
381 | }
382 | };
383 |
384 | /*
385 | Writes Logical Screen Descriptor
386 | */
387 | GIFEncoder.prototype.writeLSD = function() {
388 | // logical screen size
389 | this.writeShort(this.width);
390 | this.writeShort(this.height);
391 |
392 | // packed fields
393 | this.out.writeByte(
394 | 0x80 | // 1 : global color table flag = 1 (gct used)
395 | 0x70 | // 2-4 : color resolution = 7
396 | 0x00 | // 5 : gct sort flag = 0
397 | this.palSize // 6-8 : gct size
398 | );
399 |
400 | this.out.writeByte(0); // background color index
401 | this.out.writeByte(0); // pixel aspect ratio - assume 1:1
402 | };
403 |
404 | /*
405 | Writes Netscape application extension to define repeat count.
406 | */
407 | GIFEncoder.prototype.writeNetscapeExt = function() {
408 | this.out.writeByte(0x21); // extension introducer
409 | this.out.writeByte(0xff); // app extension label
410 | this.out.writeByte(11); // block size
411 | this.out.writeUTFBytes('NETSCAPE2.0'); // app id + auth code
412 | this.out.writeByte(3); // sub-block size
413 | this.out.writeByte(1); // loop sub-block id
414 | this.writeShort(this.repeat); // loop count (extra iterations, 0=repeat forever)
415 | this.out.writeByte(0); // block terminator
416 | };
417 |
418 | /*
419 | Writes color table
420 | */
421 | GIFEncoder.prototype.writePalette = function() {
422 | this.out.writeBytes(this.colorTab);
423 | var n = (3 * 256) - this.colorTab.length;
424 | for (var i = 0; i < n; i++)
425 | this.out.writeByte(0);
426 | };
427 |
428 | GIFEncoder.prototype.writeShort = function(pValue) {
429 | this.out.writeByte(pValue & 0xFF);
430 | this.out.writeByte((pValue >> 8) & 0xFF);
431 | };
432 |
433 | /*
434 | Encodes and writes pixel data
435 | */
436 | GIFEncoder.prototype.writePixels = function() {
437 | var enc = new LZWEncoder(this.width, this.height, this.indexedPixels, this.colorDepth);
438 | enc.encode(this.out);
439 | };
440 |
441 | self.GIFEncoder = GIFEncoder;
442 | module.exports = GIFEncoder;
443 | },{"./LZWEncoder.js":2,"./TypedNeuQuant.js":3}],2:[function(require,module,exports){
444 | /*
445 | LZWEncoder.js
446 |
447 | Authors
448 | Kevin Weiner (original Java version - kweiner@fmsware.com)
449 | Thibault Imbert (AS3 version - bytearray.org)
450 | Johan Nordberg (JS version - code@johan-nordberg.com)
451 |
452 | Acknowledgements
453 | GIFCOMPR.C - GIF Image compression routines
454 | Lempel-Ziv compression based on 'compress'. GIF modifications by
455 | David Rowley (mgardi@watdcsu.waterloo.edu)
456 | GIF Image compression - modified 'compress'
457 | Based on: compress.c - File compression ala IEEE Computer, June 1984.
458 | By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
459 | Jim McKie (decvax!mcvax!jim)
460 | Steve Davies (decvax!vax135!petsd!peora!srd)
461 | Ken Turkowski (decvax!decwrl!turtlevax!ken)
462 | James A. Woods (decvax!ihnp4!ames!jaw)
463 | Joe Orost (decvax!vax135!petsd!joe)
464 | */
465 |
466 | var EOF = -1;
467 | var BITS = 12;
468 | var HSIZE = 5003; // 80% occupancy
469 | var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
470 | 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
471 | 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
472 |
473 | function LZWEncoder(width, height, pixels, colorDepth) {
474 | var initCodeSize = Math.max(2, colorDepth);
475 |
476 | var accum = new Uint8Array(256);
477 | var htab = new Int32Array(HSIZE);
478 | var codetab = new Int32Array(HSIZE);
479 |
480 | var cur_accum, cur_bits = 0;
481 | var a_count;
482 | var free_ent = 0; // first unused entry
483 | var maxcode;
484 |
485 | // block compression parameters -- after all codes are used up,
486 | // and compression rate changes, start over.
487 | var clear_flg = false;
488 |
489 | // Algorithm: use open addressing double hashing (no chaining) on the
490 | // prefix code / next character combination. We do a variant of Knuth's
491 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
492 | // secondary probe. Here, the modular division first probe is gives way
493 | // to a faster exclusive-or manipulation. Also do block compression with
494 | // an adaptive reset, whereby the code table is cleared when the compression
495 | // ratio decreases, but after the table fills. The variable-length output
496 | // codes are re-sized at this point, and a special CLEAR code is generated
497 | // for the decompressor. Late addition: construct the table according to
498 | // file size for noticeable speed improvement on small files. Please direct
499 | // questions about this implementation to ames!jaw.
500 | var g_init_bits, ClearCode, EOFCode;
501 |
502 | // Add a character to the end of the current packet, and if it is 254
503 | // characters, flush the packet to disk.
504 | function char_out(c, outs) {
505 | accum[a_count++] = c;
506 | if (a_count >= 254) flush_char(outs);
507 | }
508 |
509 | // Clear out the hash table
510 | // table clear for block compress
511 | function cl_block(outs) {
512 | cl_hash(HSIZE);
513 | free_ent = ClearCode + 2;
514 | clear_flg = true;
515 | output(ClearCode, outs);
516 | }
517 |
518 | // Reset code table
519 | function cl_hash(hsize) {
520 | for (var i = 0; i < hsize; ++i) htab[i] = -1;
521 | }
522 |
523 | function compress(init_bits, outs) {
524 | var fcode, c, i, ent, disp, hsize_reg, hshift;
525 |
526 | // Set up the globals: g_init_bits - initial number of bits
527 | g_init_bits = init_bits;
528 |
529 | // Set up the necessary values
530 | clear_flg = false;
531 | n_bits = g_init_bits;
532 | maxcode = MAXCODE(n_bits);
533 |
534 | ClearCode = 1 << (init_bits - 1);
535 | EOFCode = ClearCode + 1;
536 | free_ent = ClearCode + 2;
537 |
538 | a_count = 0; // clear packet
539 |
540 | ent = nextPixel();
541 |
542 | hshift = 0;
543 | for (fcode = HSIZE; fcode < 65536; fcode *= 2) ++hshift;
544 | hshift = 8 - hshift; // set hash code range bound
545 | hsize_reg = HSIZE;
546 | cl_hash(hsize_reg); // clear hash table
547 |
548 | output(ClearCode, outs);
549 |
550 | outer_loop: while ((c = nextPixel()) != EOF) {
551 | fcode = (c << BITS) + ent;
552 | i = (c << hshift) ^ ent; // xor hashing
553 | if (htab[i] === fcode) {
554 | ent = codetab[i];
555 | continue;
556 | } else if (htab[i] >= 0) { // non-empty slot
557 | disp = hsize_reg - i; // secondary hash (after G. Knott)
558 | if (i === 0) disp = 1;
559 | do {
560 | if ((i -= disp) < 0) i += hsize_reg;
561 | if (htab[i] === fcode) {
562 | ent = codetab[i];
563 | continue outer_loop;
564 | }
565 | } while (htab[i] >= 0);
566 | }
567 | output(ent, outs);
568 | ent = c;
569 | if (free_ent < 1 << BITS) {
570 | codetab[i] = free_ent++; // code -> hashtable
571 | htab[i] = fcode;
572 | } else {
573 | cl_block(outs);
574 | }
575 | }
576 |
577 | // Put out the final code.
578 | output(ent, outs);
579 | output(EOFCode, outs);
580 | }
581 |
582 | function encode(outs) {
583 | outs.writeByte(initCodeSize); // write "initial code size" byte
584 | remaining = width * height; // reset navigation variables
585 | curPixel = 0;
586 | compress(initCodeSize + 1, outs); // compress and write the pixel data
587 | outs.writeByte(0); // write block terminator
588 | }
589 |
590 | // Flush the packet to disk, and reset the accumulator
591 | function flush_char(outs) {
592 | if (a_count > 0) {
593 | outs.writeByte(a_count);
594 | outs.writeBytes(accum, 0, a_count);
595 | a_count = 0;
596 | }
597 | }
598 |
599 | function MAXCODE(n_bits) {
600 | return (1 << n_bits) - 1;
601 | }
602 |
603 | // Return the next pixel from the image
604 | function nextPixel() {
605 | if (remaining === 0) return EOF;
606 | --remaining;
607 | var pix = pixels[curPixel++];
608 | return pix & 0xff;
609 | }
610 |
611 | function output(code, outs) {
612 | cur_accum &= masks[cur_bits];
613 |
614 | if (cur_bits > 0) cur_accum |= (code << cur_bits);
615 | else cur_accum = code;
616 |
617 | cur_bits += n_bits;
618 |
619 | while (cur_bits >= 8) {
620 | char_out((cur_accum & 0xff), outs);
621 | cur_accum >>= 8;
622 | cur_bits -= 8;
623 | }
624 |
625 | // If the next entry is going to be too big for the code size,
626 | // then increase it, if possible.
627 | if (free_ent > maxcode || clear_flg) {
628 | if (clear_flg) {
629 | maxcode = MAXCODE(n_bits = g_init_bits);
630 | clear_flg = false;
631 | } else {
632 | ++n_bits;
633 | if (n_bits == BITS) maxcode = 1 << BITS;
634 | else maxcode = MAXCODE(n_bits);
635 | }
636 | }
637 |
638 | if (code == EOFCode) {
639 | // At EOF, write the rest of the buffer.
640 | while (cur_bits > 0) {
641 | char_out((cur_accum & 0xff), outs);
642 | cur_accum >>= 8;
643 | cur_bits -= 8;
644 | }
645 | flush_char(outs);
646 | }
647 | }
648 |
649 | this.encode = encode;
650 | }
651 |
652 | module.exports = LZWEncoder;
653 | },{}],3:[function(require,module,exports){
654 | /* NeuQuant Neural-Net Quantization Algorithm
655 | * ------------------------------------------
656 | *
657 | * Copyright (c) 1994 Anthony Dekker
658 | *
659 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
660 | * See "Kohonen neural networks for optimal colour quantization"
661 | * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
662 | * for a discussion of the algorithm.
663 | * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
664 | *
665 | * Any party obtaining a copy of these files from the author, directly or
666 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
667 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal
668 | * in this software and documentation files (the "Software"), including without
669 | * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
670 | * and/or sell copies of the Software, and to permit persons who receive
671 | * copies from any such party to do so, with the only requirement being
672 | * that this copyright notice remain intact.
673 | *
674 | * (JavaScript port 2012 by Johan Nordberg)
675 | */
676 |
677 | var ncycles = 100; // number of learning cycles
678 | var netsize = 256; // number of colors used
679 | var maxnetpos = netsize - 1;
680 |
681 | // defs for freq and bias
682 | var netbiasshift = 4; // bias for colour values
683 | var intbiasshift = 16; // bias for fractions
684 | var intbias = (1 << intbiasshift);
685 | var gammashift = 10;
686 | var gamma = (1 << gammashift);
687 | var betashift = 10;
688 | var beta = (intbias >> betashift); /* beta = 1/1024 */
689 | var betagamma = (intbias << (gammashift - betashift));
690 |
691 | // defs for decreasing radius factor
692 | var initrad = (netsize >> 3); // for 256 cols, radius starts
693 | var radiusbiasshift = 6; // at 32.0 biased by 6 bits
694 | var radiusbias = (1 << radiusbiasshift);
695 | var initradius = (initrad * radiusbias); //and decreases by a
696 | var radiusdec = 30; // factor of 1/30 each cycle
697 |
698 | // defs for decreasing alpha factor
699 | var alphabiasshift = 10; // alpha starts at 1.0
700 | var initalpha = (1 << alphabiasshift);
701 | var alphadec; // biased by 10 bits
702 |
703 | /* radbias and alpharadbias used for radpower calculation */
704 | var radbiasshift = 8;
705 | var radbias = (1 << radbiasshift);
706 | var alpharadbshift = (alphabiasshift + radbiasshift);
707 | var alpharadbias = (1 << alpharadbshift);
708 |
709 | // four primes near 500 - assume no image has a length so large that it is
710 | // divisible by all four primes
711 | var prime1 = 499;
712 | var prime2 = 491;
713 | var prime3 = 487;
714 | var prime4 = 503;
715 | var minpicturebytes = (3 * prime4);
716 |
717 | /*
718 | Constructor: NeuQuant
719 |
720 | Arguments:
721 |
722 | pixels - array of pixels in RGB format
723 | samplefac - sampling factor 1 to 30 where lower is better quality
724 |
725 | >
726 | > pixels = [r, g, b, r, g, b, r, g, b, ..]
727 | >
728 | */
729 | function NeuQuant(pixels, samplefac) {
730 | var network; // int[netsize][4]
731 | var netindex; // for network lookup - really 256
732 |
733 | // bias and freq arrays for learning
734 | var bias;
735 | var freq;
736 | var radpower;
737 |
738 | /*
739 | Private Method: init
740 |
741 | sets up arrays
742 | */
743 | function init() {
744 | network = [];
745 | netindex = new Int32Array(256);
746 | bias = new Int32Array(netsize);
747 | freq = new Int32Array(netsize);
748 | radpower = new Int32Array(netsize >> 3);
749 |
750 | var i, v;
751 | for (i = 0; i < netsize; i++) {
752 | v = (i << (netbiasshift + 8)) / netsize;
753 | network[i] = new Float64Array([v, v, v, 0]);
754 | //network[i] = [v, v, v, 0]
755 | freq[i] = intbias / netsize;
756 | bias[i] = 0;
757 | }
758 | }
759 |
760 | /*
761 | Private Method: unbiasnet
762 |
763 | unbiases network to give byte values 0..255 and record position i to prepare for sort
764 | */
765 | function unbiasnet() {
766 | for (var i = 0; i < netsize; i++) {
767 | network[i][0] >>= netbiasshift;
768 | network[i][1] >>= netbiasshift;
769 | network[i][2] >>= netbiasshift;
770 | network[i][3] = i; // record color number
771 | }
772 | }
773 |
774 | /*
775 | Private Method: altersingle
776 |
777 | moves neuron *i* towards biased (b,g,r) by factor *alpha*
778 | */
779 | function altersingle(alpha, i, b, g, r) {
780 | network[i][0] -= (alpha * (network[i][0] - b)) / initalpha;
781 | network[i][1] -= (alpha * (network[i][1] - g)) / initalpha;
782 | network[i][2] -= (alpha * (network[i][2] - r)) / initalpha;
783 | }
784 |
785 | /*
786 | Private Method: alterneigh
787 |
788 | moves neurons in *radius* around index *i* towards biased (b,g,r) by factor *alpha*
789 | */
790 | function alterneigh(radius, i, b, g, r) {
791 | var lo = Math.abs(i - radius);
792 | var hi = Math.min(i + radius, netsize);
793 |
794 | var j = i + 1;
795 | var k = i - 1;
796 | var m = 1;
797 |
798 | var p, a;
799 | while ((j < hi) || (k > lo)) {
800 | a = radpower[m++];
801 |
802 | if (j < hi) {
803 | p = network[j++];
804 | p[0] -= (a * (p[0] - b)) / alpharadbias;
805 | p[1] -= (a * (p[1] - g)) / alpharadbias;
806 | p[2] -= (a * (p[2] - r)) / alpharadbias;
807 | }
808 |
809 | if (k > lo) {
810 | p = network[k--];
811 | p[0] -= (a * (p[0] - b)) / alpharadbias;
812 | p[1] -= (a * (p[1] - g)) / alpharadbias;
813 | p[2] -= (a * (p[2] - r)) / alpharadbias;
814 | }
815 | }
816 | }
817 |
818 | /*
819 | Private Method: contest
820 |
821 | searches for biased BGR values
822 | */
823 | function contest(b, g, r) {
824 | /*
825 | finds closest neuron (min dist) and updates freq
826 | finds best neuron (min dist-bias) and returns position
827 | for frequently chosen neurons, freq[i] is high and bias[i] is negative
828 | bias[i] = gamma * ((1 / netsize) - freq[i])
829 | */
830 |
831 | var bestd = ~(1 << 31);
832 | var bestbiasd = bestd;
833 | var bestpos = -1;
834 | var bestbiaspos = bestpos;
835 |
836 | var i, n, dist, biasdist, betafreq;
837 | for (i = 0; i < netsize; i++) {
838 | n = network[i];
839 |
840 | dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r);
841 | if (dist < bestd) {
842 | bestd = dist;
843 | bestpos = i;
844 | }
845 |
846 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
847 | if (biasdist < bestbiasd) {
848 | bestbiasd = biasdist;
849 | bestbiaspos = i;
850 | }
851 |
852 | betafreq = (freq[i] >> betashift);
853 | freq[i] -= betafreq;
854 | bias[i] += (betafreq << gammashift);
855 | }
856 |
857 | freq[bestpos] += beta;
858 | bias[bestpos] -= betagamma;
859 |
860 | return bestbiaspos;
861 | }
862 |
863 | /*
864 | Private Method: inxbuild
865 |
866 | sorts network and builds netindex[0..255]
867 | */
868 | function inxbuild() {
869 | var i, j, p, q, smallpos, smallval, previouscol = 0, startpos = 0;
870 | for (i = 0; i < netsize; i++) {
871 | p = network[i];
872 | smallpos = i;
873 | smallval = p[1]; // index on g
874 | // find smallest in i..netsize-1
875 | for (j = i + 1; j < netsize; j++) {
876 | q = network[j];
877 | if (q[1] < smallval) { // index on g
878 | smallpos = j;
879 | smallval = q[1]; // index on g
880 | }
881 | }
882 | q = network[smallpos];
883 | // swap p (i) and q (smallpos) entries
884 | if (i != smallpos) {
885 | j = q[0]; q[0] = p[0]; p[0] = j;
886 | j = q[1]; q[1] = p[1]; p[1] = j;
887 | j = q[2]; q[2] = p[2]; p[2] = j;
888 | j = q[3]; q[3] = p[3]; p[3] = j;
889 | }
890 | // smallval entry is now in position i
891 |
892 | if (smallval != previouscol) {
893 | netindex[previouscol] = (startpos + i) >> 1;
894 | for (j = previouscol + 1; j < smallval; j++)
895 | netindex[j] = i;
896 | previouscol = smallval;
897 | startpos = i;
898 | }
899 | }
900 | netindex[previouscol] = (startpos + maxnetpos) >> 1;
901 | for (j = previouscol + 1; j < 256; j++)
902 | netindex[j] = maxnetpos; // really 256
903 | }
904 |
905 | /*
906 | Private Method: inxsearch
907 |
908 | searches for BGR values 0..255 and returns a color index
909 | */
910 | function inxsearch(b, g, r) {
911 | var a, p, dist;
912 |
913 | var bestd = 1000; // biggest possible dist is 256*3
914 | var best = -1;
915 |
916 | var i = netindex[g]; // index on g
917 | var j = i - 1; // start at netindex[g] and work outwards
918 |
919 | while ((i < netsize) || (j >= 0)) {
920 | if (i < netsize) {
921 | p = network[i];
922 | dist = p[1] - g; // inx key
923 | if (dist >= bestd) i = netsize; // stop iter
924 | else {
925 | i++;
926 | if (dist < 0) dist = -dist;
927 | a = p[0] - b; if (a < 0) a = -a;
928 | dist += a;
929 | if (dist < bestd) {
930 | a = p[2] - r; if (a < 0) a = -a;
931 | dist += a;
932 | if (dist < bestd) {
933 | bestd = dist;
934 | best = p[3];
935 | }
936 | }
937 | }
938 | }
939 | if (j >= 0) {
940 | p = network[j];
941 | dist = g - p[1]; // inx key - reverse dif
942 | if (dist >= bestd) j = -1; // stop iter
943 | else {
944 | j--;
945 | if (dist < 0) dist = -dist;
946 | a = p[0] - b; if (a < 0) a = -a;
947 | dist += a;
948 | if (dist < bestd) {
949 | a = p[2] - r; if (a < 0) a = -a;
950 | dist += a;
951 | if (dist < bestd) {
952 | bestd = dist;
953 | best = p[3];
954 | }
955 | }
956 | }
957 | }
958 | }
959 |
960 | return best;
961 | }
962 |
963 | /*
964 | Private Method: learn
965 |
966 | "Main Learning Loop"
967 | */
968 | function learn() {
969 | var i;
970 |
971 | var lengthcount = pixels.length;
972 | var alphadec = 30 + ((samplefac - 1) / 3);
973 | var samplepixels = lengthcount / (3 * samplefac);
974 | var delta = ~~(samplepixels / ncycles);
975 | var alpha = initalpha;
976 | var radius = initradius;
977 |
978 | var rad = radius >> radiusbiasshift;
979 |
980 | if (rad <= 1) rad = 0;
981 | for (i = 0; i < rad; i++)
982 | radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
983 |
984 | var step;
985 | if (lengthcount < minpicturebytes) {
986 | samplefac = 1;
987 | step = 3;
988 | } else if ((lengthcount % prime1) !== 0) {
989 | step = 3 * prime1;
990 | } else if ((lengthcount % prime2) !== 0) {
991 | step = 3 * prime2;
992 | } else if ((lengthcount % prime3) !== 0) {
993 | step = 3 * prime3;
994 | } else {
995 | step = 3 * prime4;
996 | }
997 |
998 | var b, g, r, j;
999 | var pix = 0; // current pixel
1000 |
1001 | i = 0;
1002 | while (i < samplepixels) {
1003 | b = (pixels[pix] & 0xff) << netbiasshift;
1004 | g = (pixels[pix + 1] & 0xff) << netbiasshift;
1005 | r = (pixels[pix + 2] & 0xff) << netbiasshift;
1006 |
1007 | j = contest(b, g, r);
1008 |
1009 | altersingle(alpha, j, b, g, r);
1010 | if (rad !== 0) alterneigh(rad, j, b, g, r); // alter neighbours
1011 |
1012 | pix += step;
1013 | if (pix >= lengthcount) pix -= lengthcount;
1014 |
1015 | i++;
1016 |
1017 | if (delta === 0) delta = 1;
1018 | if (i % delta === 0) {
1019 | alpha -= alpha / alphadec;
1020 | radius -= radius / radiusdec;
1021 | rad = radius >> radiusbiasshift;
1022 |
1023 | if (rad <= 1) rad = 0;
1024 | for (j = 0; j < rad; j++)
1025 | radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
1026 | }
1027 | }
1028 | }
1029 |
1030 | /*
1031 | Method: buildColormap
1032 |
1033 | 1. initializes network
1034 | 2. trains it
1035 | 3. removes misconceptions
1036 | 4. builds colorindex
1037 | */
1038 | function buildColormap() {
1039 | init();
1040 | learn();
1041 | unbiasnet();
1042 | inxbuild();
1043 | }
1044 | this.buildColormap = buildColormap;
1045 |
1046 | /*
1047 | Method: getColormap
1048 |
1049 | builds colormap from the index
1050 |
1051 | returns array in the format:
1052 |
1053 | >
1054 | > [r, g, b, r, g, b, r, g, b, ..]
1055 | >
1056 | */
1057 | function getColormap() {
1058 | var map = [];
1059 | var index = [];
1060 |
1061 | for (var i = 0; i < netsize; i++)
1062 | index[network[i][3]] = i;
1063 |
1064 | var k = 0;
1065 | for (var l = 0; l < netsize; l++) {
1066 | var j = index[l];
1067 | map[k++] = (network[j][0]);
1068 | map[k++] = (network[j][1]);
1069 | map[k++] = (network[j][2]);
1070 | }
1071 | return map;
1072 | }
1073 | this.getColormap = getColormap;
1074 |
1075 | /*
1076 | Method: lookupRGB
1077 |
1078 | looks for the closest *r*, *g*, *b* color in the map and
1079 | returns its index
1080 | */
1081 | this.lookupRGB = inxsearch;
1082 | }
1083 |
1084 | module.exports = NeuQuant;
1085 | },{}]},{},[1]);
1086 |
--------------------------------------------------------------------------------
/src/demos/gif-stream/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 | Loading service worker…
13 |
14 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/src/demos/gif-stream/regenerator-runtime.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
7 | * additional grant of patent rights can be found in the PATENTS file in
8 | * the same directory.
9 | */
10 |
11 | !(function(global) {
12 | "use strict";
13 |
14 | var hasOwn = Object.prototype.hasOwnProperty;
15 | var undefined; // More compressible than void 0.
16 | var $Symbol = typeof Symbol === "function" ? Symbol : {};
17 | var iteratorSymbol = $Symbol.iterator || "@@iterator";
18 | var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";
19 |
20 | var inModule = typeof module === "object";
21 | var runtime = global.regeneratorRuntime;
22 | if (runtime) {
23 | if (inModule) {
24 | // If regeneratorRuntime is defined globally and we're in a module,
25 | // make the exports object identical to regeneratorRuntime.
26 | module.exports = runtime;
27 | }
28 | // Don't bother evaluating the rest of this file if the runtime was
29 | // already defined globally.
30 | return;
31 | }
32 |
33 | // Define the runtime globally (as expected by generated code) as either
34 | // module.exports (if we're in a module) or a new, empty object.
35 | runtime = global.regeneratorRuntime = inModule ? module.exports : {};
36 |
37 | function wrap(innerFn, outerFn, self, tryLocsList) {
38 | // If outerFn provided, then outerFn.prototype instanceof Generator.
39 | var generator = Object.create((outerFn || Generator).prototype);
40 | var context = new Context(tryLocsList || []);
41 |
42 | // The ._invoke method unifies the implementations of the .next,
43 | // .throw, and .return methods.
44 | generator._invoke = makeInvokeMethod(innerFn, self, context);
45 |
46 | return generator;
47 | }
48 | runtime.wrap = wrap;
49 |
50 | // Try/catch helper to minimize deoptimizations. Returns a completion
51 | // record like context.tryEntries[i].completion. This interface could
52 | // have been (and was previously) designed to take a closure to be
53 | // invoked without arguments, but in all the cases we care about we
54 | // already have an existing method we want to call, so there's no need
55 | // to create a new function object. We can even get away with assuming
56 | // the method takes exactly one argument, since that happens to be true
57 | // in every case, so we don't have to touch the arguments object. The
58 | // only additional allocation required is the completion record, which
59 | // has a stable shape and so hopefully should be cheap to allocate.
60 | function tryCatch(fn, obj, arg) {
61 | try {
62 | return { type: "normal", arg: fn.call(obj, arg) };
63 | } catch (err) {
64 | return { type: "throw", arg: err };
65 | }
66 | }
67 |
68 | var GenStateSuspendedStart = "suspendedStart";
69 | var GenStateSuspendedYield = "suspendedYield";
70 | var GenStateExecuting = "executing";
71 | var GenStateCompleted = "completed";
72 |
73 | // Returning this object from the innerFn has the same effect as
74 | // breaking out of the dispatch switch statement.
75 | var ContinueSentinel = {};
76 |
77 | // Dummy constructor functions that we use as the .constructor and
78 | // .constructor.prototype properties for functions that return Generator
79 | // objects. For full spec compliance, you may wish to configure your
80 | // minifier not to mangle the names of these two functions.
81 | function Generator() {}
82 | function GeneratorFunction() {}
83 | function GeneratorFunctionPrototype() {}
84 |
85 | var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype;
86 | GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
87 | GeneratorFunctionPrototype.constructor = GeneratorFunction;
88 | GeneratorFunctionPrototype[toStringTagSymbol] = GeneratorFunction.displayName = "GeneratorFunction";
89 |
90 | // Helper for defining the .next, .throw, and .return methods of the
91 | // Iterator interface in terms of a single ._invoke method.
92 | function defineIteratorMethods(prototype) {
93 | ["next", "throw", "return"].forEach(function(method) {
94 | prototype[method] = function(arg) {
95 | return this._invoke(method, arg);
96 | };
97 | });
98 | }
99 |
100 | runtime.isGeneratorFunction = function(genFun) {
101 | var ctor = typeof genFun === "function" && genFun.constructor;
102 | return ctor
103 | ? ctor === GeneratorFunction ||
104 | // For the native GeneratorFunction constructor, the best we can
105 | // do is to check its .name property.
106 | (ctor.displayName || ctor.name) === "GeneratorFunction"
107 | : false;
108 | };
109 |
110 | runtime.mark = function(genFun) {
111 | if (Object.setPrototypeOf) {
112 | Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
113 | } else {
114 | genFun.__proto__ = GeneratorFunctionPrototype;
115 | if (!(toStringTagSymbol in genFun)) {
116 | genFun[toStringTagSymbol] = "GeneratorFunction";
117 | }
118 | }
119 | genFun.prototype = Object.create(Gp);
120 | return genFun;
121 | };
122 |
123 | // Within the body of any async function, `await x` is transformed to
124 | // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
125 | // `value instanceof AwaitArgument` to determine if the yielded value is
126 | // meant to be awaited. Some may consider the name of this method too
127 | // cutesy, but they are curmudgeons.
128 | runtime.awrap = function(arg) {
129 | return new AwaitArgument(arg);
130 | };
131 |
132 | function AwaitArgument(arg) {
133 | this.arg = arg;
134 | }
135 |
136 | function AsyncIterator(generator) {
137 | function invoke(method, arg, resolve, reject) {
138 | var record = tryCatch(generator[method], generator, arg);
139 | if (record.type === "throw") {
140 | reject(record.arg);
141 | } else {
142 | var result = record.arg;
143 | var value = result.value;
144 | if (value instanceof AwaitArgument) {
145 | return Promise.resolve(value.arg).then(function(value) {
146 | invoke("next", value, resolve, reject);
147 | }, function(err) {
148 | invoke("throw", err, resolve, reject);
149 | });
150 | }
151 |
152 | return Promise.resolve(value).then(function(unwrapped) {
153 | // When a yielded Promise is resolved, its final value becomes
154 | // the .value of the Promise<{value,done}> result for the
155 | // current iteration. If the Promise is rejected, however, the
156 | // result for this iteration will be rejected with the same
157 | // reason. Note that rejections of yielded Promises are not
158 | // thrown back into the generator function, as is the case
159 | // when an awaited Promise is rejected. This difference in
160 | // behavior between yield and await is important, because it
161 | // allows the consumer to decide what to do with the yielded
162 | // rejection (swallow it and continue, manually .throw it back
163 | // into the generator, abandon iteration, whatever). With
164 | // await, by contrast, there is no opportunity to examine the
165 | // rejection reason outside the generator function, so the
166 | // only option is to throw it from the await expression, and
167 | // let the generator function handle the exception.
168 | result.value = unwrapped;
169 | resolve(result);
170 | }, reject);
171 | }
172 | }
173 |
174 | if (typeof process === "object" && process.domain) {
175 | invoke = process.domain.bind(invoke);
176 | }
177 |
178 | var previousPromise;
179 |
180 | function enqueue(method, arg) {
181 | function callInvokeWithMethodAndArg() {
182 | return new Promise(function(resolve, reject) {
183 | invoke(method, arg, resolve, reject);
184 | });
185 | }
186 |
187 | return previousPromise =
188 | // If enqueue has been called before, then we want to wait until
189 | // all previous Promises have been resolved before calling invoke,
190 | // so that results are always delivered in the correct order. If
191 | // enqueue has not been called before, then it is important to
192 | // call invoke immediately, without waiting on a callback to fire,
193 | // so that the async generator function has the opportunity to do
194 | // any necessary setup in a predictable way. This predictability
195 | // is why the Promise constructor synchronously invokes its
196 | // executor callback, and why async functions synchronously
197 | // execute code before the first await. Since we implement simple
198 | // async functions in terms of async generators, it is especially
199 | // important to get this right, even though it requires care.
200 | previousPromise ? previousPromise.then(
201 | callInvokeWithMethodAndArg,
202 | // Avoid propagating failures to Promises returned by later
203 | // invocations of the iterator.
204 | callInvokeWithMethodAndArg
205 | ) : callInvokeWithMethodAndArg();
206 | }
207 |
208 | // Define the unified helper method that is used to implement .next,
209 | // .throw, and .return (see defineIteratorMethods).
210 | this._invoke = enqueue;
211 | }
212 |
213 | defineIteratorMethods(AsyncIterator.prototype);
214 |
215 | // Note that simple async functions are implemented on top of
216 | // AsyncIterator objects; they just return a Promise for the value of
217 | // the final result produced by the iterator.
218 | runtime.async = function(innerFn, outerFn, self, tryLocsList) {
219 | var iter = new AsyncIterator(
220 | wrap(innerFn, outerFn, self, tryLocsList)
221 | );
222 |
223 | return runtime.isGeneratorFunction(outerFn)
224 | ? iter // If outerFn is a generator, return the full iterator.
225 | : iter.next().then(function(result) {
226 | return result.done ? result.value : iter.next();
227 | });
228 | };
229 |
230 | function makeInvokeMethod(innerFn, self, context) {
231 | var state = GenStateSuspendedStart;
232 |
233 | return function invoke(method, arg) {
234 | if (state === GenStateExecuting) {
235 | throw new Error("Generator is already running");
236 | }
237 |
238 | if (state === GenStateCompleted) {
239 | if (method === "throw") {
240 | throw arg;
241 | }
242 |
243 | // Be forgiving, per 25.3.3.3.3 of the spec:
244 | // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
245 | return doneResult();
246 | }
247 |
248 | while (true) {
249 | var delegate = context.delegate;
250 | if (delegate) {
251 | if (method === "return" ||
252 | (method === "throw" && delegate.iterator[method] === undefined)) {
253 | // A return or throw (when the delegate iterator has no throw
254 | // method) always terminates the yield* loop.
255 | context.delegate = null;
256 |
257 | // If the delegate iterator has a return method, give it a
258 | // chance to clean up.
259 | var returnMethod = delegate.iterator["return"];
260 | if (returnMethod) {
261 | var record = tryCatch(returnMethod, delegate.iterator, arg);
262 | if (record.type === "throw") {
263 | // If the return method threw an exception, let that
264 | // exception prevail over the original return or throw.
265 | method = "throw";
266 | arg = record.arg;
267 | continue;
268 | }
269 | }
270 |
271 | if (method === "return") {
272 | // Continue with the outer return, now that the delegate
273 | // iterator has been terminated.
274 | continue;
275 | }
276 | }
277 |
278 | var record = tryCatch(
279 | delegate.iterator[method],
280 | delegate.iterator,
281 | arg
282 | );
283 |
284 | if (record.type === "throw") {
285 | context.delegate = null;
286 |
287 | // Like returning generator.throw(uncaught), but without the
288 | // overhead of an extra function call.
289 | method = "throw";
290 | arg = record.arg;
291 | continue;
292 | }
293 |
294 | // Delegate generator ran and handled its own exceptions so
295 | // regardless of what the method was, we continue as if it is
296 | // "next" with an undefined arg.
297 | method = "next";
298 | arg = undefined;
299 |
300 | var info = record.arg;
301 | if (info.done) {
302 | context[delegate.resultName] = info.value;
303 | context.next = delegate.nextLoc;
304 | } else {
305 | state = GenStateSuspendedYield;
306 | return info;
307 | }
308 |
309 | context.delegate = null;
310 | }
311 |
312 | if (method === "next") {
313 | if (state === GenStateSuspendedYield) {
314 | context.sent = arg;
315 | } else {
316 | context.sent = undefined;
317 | }
318 |
319 | } else if (method === "throw") {
320 | if (state === GenStateSuspendedStart) {
321 | state = GenStateCompleted;
322 | throw arg;
323 | }
324 |
325 | if (context.dispatchException(arg)) {
326 | // If the dispatched exception was caught by a catch block,
327 | // then let that catch block handle the exception normally.
328 | method = "next";
329 | arg = undefined;
330 | }
331 |
332 | } else if (method === "return") {
333 | context.abrupt("return", arg);
334 | }
335 |
336 | state = GenStateExecuting;
337 |
338 | var record = tryCatch(innerFn, self, context);
339 | if (record.type === "normal") {
340 | // If an exception is thrown from innerFn, we leave state ===
341 | // GenStateExecuting and loop back for another invocation.
342 | state = context.done
343 | ? GenStateCompleted
344 | : GenStateSuspendedYield;
345 |
346 | var info = {
347 | value: record.arg,
348 | done: context.done
349 | };
350 |
351 | if (record.arg === ContinueSentinel) {
352 | if (context.delegate && method === "next") {
353 | // Deliberately forget the last sent value so that we don't
354 | // accidentally pass it on to the delegate.
355 | arg = undefined;
356 | }
357 | } else {
358 | return info;
359 | }
360 |
361 | } else if (record.type === "throw") {
362 | state = GenStateCompleted;
363 | // Dispatch the exception by looping back around to the
364 | // context.dispatchException(arg) call above.
365 | method = "throw";
366 | arg = record.arg;
367 | }
368 | }
369 | };
370 | }
371 |
372 | // Define Generator.prototype.{next,throw,return} in terms of the
373 | // unified ._invoke helper method.
374 | defineIteratorMethods(Gp);
375 |
376 | Gp[iteratorSymbol] = function() {
377 | return this;
378 | };
379 |
380 | Gp[toStringTagSymbol] = "Generator";
381 |
382 | Gp.toString = function() {
383 | return "[object Generator]";
384 | };
385 |
386 | function pushTryEntry(locs) {
387 | var entry = { tryLoc: locs[0] };
388 |
389 | if (1 in locs) {
390 | entry.catchLoc = locs[1];
391 | }
392 |
393 | if (2 in locs) {
394 | entry.finallyLoc = locs[2];
395 | entry.afterLoc = locs[3];
396 | }
397 |
398 | this.tryEntries.push(entry);
399 | }
400 |
401 | function resetTryEntry(entry) {
402 | var record = entry.completion || {};
403 | record.type = "normal";
404 | delete record.arg;
405 | entry.completion = record;
406 | }
407 |
408 | function Context(tryLocsList) {
409 | // The root entry object (effectively a try statement without a catch
410 | // or a finally block) gives us a place to store values thrown from
411 | // locations where there is no enclosing try statement.
412 | this.tryEntries = [{ tryLoc: "root" }];
413 | tryLocsList.forEach(pushTryEntry, this);
414 | this.reset(true);
415 | }
416 |
417 | runtime.keys = function(object) {
418 | var keys = [];
419 | for (var key in object) {
420 | keys.push(key);
421 | }
422 | keys.reverse();
423 |
424 | // Rather than returning an object with a next method, we keep
425 | // things simple and return the next function itself.
426 | return function next() {
427 | while (keys.length) {
428 | var key = keys.pop();
429 | if (key in object) {
430 | next.value = key;
431 | next.done = false;
432 | return next;
433 | }
434 | }
435 |
436 | // To avoid creating an additional object, we just hang the .value
437 | // and .done properties off the next function object itself. This
438 | // also ensures that the minifier will not anonymize the function.
439 | next.done = true;
440 | return next;
441 | };
442 | };
443 |
444 | function values(iterable) {
445 | if (iterable) {
446 | var iteratorMethod = iterable[iteratorSymbol];
447 | if (iteratorMethod) {
448 | return iteratorMethod.call(iterable);
449 | }
450 |
451 | if (typeof iterable.next === "function") {
452 | return iterable;
453 | }
454 |
455 | if (!isNaN(iterable.length)) {
456 | var i = -1, next = function next() {
457 | while (++i < iterable.length) {
458 | if (hasOwn.call(iterable, i)) {
459 | next.value = iterable[i];
460 | next.done = false;
461 | return next;
462 | }
463 | }
464 |
465 | next.value = undefined;
466 | next.done = true;
467 |
468 | return next;
469 | };
470 |
471 | return next.next = next;
472 | }
473 | }
474 |
475 | // Return an iterator with no values.
476 | return { next: doneResult };
477 | }
478 | runtime.values = values;
479 |
480 | function doneResult() {
481 | return { value: undefined, done: true };
482 | }
483 |
484 | Context.prototype = {
485 | constructor: Context,
486 |
487 | reset: function(skipTempReset) {
488 | this.prev = 0;
489 | this.next = 0;
490 | this.sent = undefined;
491 | this.done = false;
492 | this.delegate = null;
493 |
494 | this.tryEntries.forEach(resetTryEntry);
495 |
496 | if (!skipTempReset) {
497 | for (var name in this) {
498 | // Not sure about the optimal order of these conditions:
499 | if (name.charAt(0) === "t" &&
500 | hasOwn.call(this, name) &&
501 | !isNaN(+name.slice(1))) {
502 | this[name] = undefined;
503 | }
504 | }
505 | }
506 | },
507 |
508 | stop: function() {
509 | this.done = true;
510 |
511 | var rootEntry = this.tryEntries[0];
512 | var rootRecord = rootEntry.completion;
513 | if (rootRecord.type === "throw") {
514 | throw rootRecord.arg;
515 | }
516 |
517 | return this.rval;
518 | },
519 |
520 | dispatchException: function(exception) {
521 | if (this.done) {
522 | throw exception;
523 | }
524 |
525 | var context = this;
526 | function handle(loc, caught) {
527 | record.type = "throw";
528 | record.arg = exception;
529 | context.next = loc;
530 | return !!caught;
531 | }
532 |
533 | for (var i = this.tryEntries.length - 1; i >= 0; --i) {
534 | var entry = this.tryEntries[i];
535 | var record = entry.completion;
536 |
537 | if (entry.tryLoc === "root") {
538 | // Exception thrown outside of any try block that could handle
539 | // it, so set the completion value of the entire function to
540 | // throw the exception.
541 | return handle("end");
542 | }
543 |
544 | if (entry.tryLoc <= this.prev) {
545 | var hasCatch = hasOwn.call(entry, "catchLoc");
546 | var hasFinally = hasOwn.call(entry, "finallyLoc");
547 |
548 | if (hasCatch && hasFinally) {
549 | if (this.prev < entry.catchLoc) {
550 | return handle(entry.catchLoc, true);
551 | } else if (this.prev < entry.finallyLoc) {
552 | return handle(entry.finallyLoc);
553 | }
554 |
555 | } else if (hasCatch) {
556 | if (this.prev < entry.catchLoc) {
557 | return handle(entry.catchLoc, true);
558 | }
559 |
560 | } else if (hasFinally) {
561 | if (this.prev < entry.finallyLoc) {
562 | return handle(entry.finallyLoc);
563 | }
564 |
565 | } else {
566 | throw new Error("try statement without catch or finally");
567 | }
568 | }
569 | }
570 | },
571 |
572 | abrupt: function(type, arg) {
573 | for (var i = this.tryEntries.length - 1; i >= 0; --i) {
574 | var entry = this.tryEntries[i];
575 | if (entry.tryLoc <= this.prev &&
576 | hasOwn.call(entry, "finallyLoc") &&
577 | this.prev < entry.finallyLoc) {
578 | var finallyEntry = entry;
579 | break;
580 | }
581 | }
582 |
583 | if (finallyEntry &&
584 | (type === "break" ||
585 | type === "continue") &&
586 | finallyEntry.tryLoc <= arg &&
587 | arg <= finallyEntry.finallyLoc) {
588 | // Ignore the finally entry if control is not jumping to a
589 | // location outside the try/catch block.
590 | finallyEntry = null;
591 | }
592 |
593 | var record = finallyEntry ? finallyEntry.completion : {};
594 | record.type = type;
595 | record.arg = arg;
596 |
597 | if (finallyEntry) {
598 | this.next = finallyEntry.finallyLoc;
599 | } else {
600 | this.complete(record);
601 | }
602 |
603 | return ContinueSentinel;
604 | },
605 |
606 | complete: function(record, afterLoc) {
607 | if (record.type === "throw") {
608 | throw record.arg;
609 | }
610 |
611 | if (record.type === "break" ||
612 | record.type === "continue") {
613 | this.next = record.arg;
614 | } else if (record.type === "return") {
615 | this.rval = record.arg;
616 | this.next = "end";
617 | } else if (record.type === "normal" && afterLoc) {
618 | this.next = afterLoc;
619 | }
620 | },
621 |
622 | finish: function(finallyLoc) {
623 | for (var i = this.tryEntries.length - 1; i >= 0; --i) {
624 | var entry = this.tryEntries[i];
625 | if (entry.finallyLoc === finallyLoc) {
626 | this.complete(entry.completion, entry.afterLoc);
627 | resetTryEntry(entry);
628 | return ContinueSentinel;
629 | }
630 | }
631 | },
632 |
633 | "catch": function(tryLoc) {
634 | for (var i = this.tryEntries.length - 1; i >= 0; --i) {
635 | var entry = this.tryEntries[i];
636 | if (entry.tryLoc === tryLoc) {
637 | var record = entry.completion;
638 | if (record.type === "throw") {
639 | var thrown = record.arg;
640 | resetTryEntry(entry);
641 | }
642 | return thrown;
643 | }
644 | }
645 |
646 | // The context.catch method must only be called with a location
647 | // argument that corresponds to a known catch block.
648 | throw new Error("illegal catch attempt");
649 | },
650 |
651 | delegateYield: function(iterable, resultName, nextLoc) {
652 | this.delegate = {
653 | iterator: values(iterable),
654 | resultName: resultName,
655 | nextLoc: nextLoc
656 | };
657 |
658 | return ContinueSentinel;
659 | }
660 | };
661 | })(
662 | // Among the various tricks for obtaining a reference to the global
663 | // object, this seems to be the most reliable technique that does not
664 | // use indirect eval (which violates Content Security Policy).
665 | typeof global === "object" ? global :
666 | typeof window === "object" ? window :
667 | typeof self === "object" ? self : this
668 | );
--------------------------------------------------------------------------------
/src/demos/gif-stream/stream.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 | Decoding MPEG
16 |
17 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/demos/gif-stream/sw.js:
--------------------------------------------------------------------------------
1 | importScripts('jsmpeg.js', 'gif.js');
2 |
3 | self.addEventListener('install', event => {
4 | self.skipWaiting();
5 | });
6 |
7 | self.addEventListener('activate', event => {
8 | clients.claim();
9 | });
10 |
11 | self.addEventListener('fetch', event => {
12 | const requestURL = new URL(event.request.url);
13 |
14 | if (requestURL.origin != location.origin) return;
15 |
16 | if (requestURL.pathname.endsWith("/demos/gif-stream/")) {
17 | event.respondWith(fetch('gif.html'));
18 | return;
19 | }
20 |
21 | if (requestURL.pathname.endsWith(".gif")) {
22 | event.respondWith(streamGIF(requestURL.pathname.replace(/\.gif$/, '.mpg')));
23 | return;
24 | }
25 | });
26 |
27 | function streamGIF(url) {
28 | return fetch(url).then(response => {
29 | const jsmpeg = new JSMPEG();
30 | const reader = response.body.getReader();
31 |
32 | function readToJSMPEG() {
33 | return reader.read().then(result => {
34 | if (result.done) {
35 | jsmpeg.writeEnd();
36 | return;
37 | }
38 | jsmpeg.write(result.value);
39 | return readToJSMPEG();
40 | });
41 | }
42 |
43 | // read the response into jsmpeg
44 | readToJSMPEG();
45 |
46 | // wait for width/height info
47 | return jsmpeg.ready.then(function() {
48 | const gif = new GIFEncoder(jsmpeg.width, jsmpeg.height);
49 | gif.start();
50 | gif.setQuality(10);
51 | gif.setRepeat(0);
52 | gif.setFrameRate(jsmpeg.pictureRate);
53 |
54 | const frameReader = jsmpeg.readable.getReader();
55 |
56 | function readFramesToGIF() {
57 | frameReader.read().then(function(result) {
58 | if (result.done) {
59 | gif.finish();
60 | return;
61 | }
62 | gif.addFrame(result.value);
63 | setTimeout(readFramesToGIF, 0);
64 | });
65 | }
66 |
67 | readFramesToGIF();
68 |
69 | return new Response(gif.readable, {
70 | headers: {'Content-Type': 'image/gif'}
71 | });
72 | });
73 | });
74 | }
--------------------------------------------------------------------------------
/src/demos/globalapis/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/src/demos/globalapis/sw.js:
--------------------------------------------------------------------------------
1 | console.log("SW startup");
2 | console.log("Request", this.Request);
3 | console.log("Response", this.Response);
4 | console.log("fetch", this.fetch);
5 | console.log("Cache", this.Cache);
6 | console.log("caches", this.caches);
7 | console.log("getAll", this.getAll);
8 |
--------------------------------------------------------------------------------
/src/demos/headers-log/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/demos/headers-log/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('fetch', function(event) {
2 | console.log("Fetching", event.request.url);
3 | console.log("Headers", new Set(event.request.headers));
4 | event.respondWith(fetch(event.request));
5 | });
6 |
--------------------------------------------------------------------------------
/src/demos/http-redirect/blah/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 | It worked!
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/demos/http-redirect/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 | This test is reliant on a bug in github pages where a
14 | relative url such as
15 | blah
will redirect to the http
16 | version of blah/
if blah/index.html
17 | exists.
18 |
19 | Click me
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/demos/http-redirect/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('activate', _ => {
2 | clients.claim();
3 | });
4 |
5 | self.addEventListener('fetch', event => {
6 | console.log(event.request);
7 | event.respondWith(
8 | fetch(event.request).catch(function() {
9 | return new Response("The fetch, it failed :(");
10 | })
11 | );
12 | });
--------------------------------------------------------------------------------
/src/demos/img-rewrite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
33 |
34 |
--------------------------------------------------------------------------------
/src/demos/img-rewrite/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('fetch', function(event) {
2 | if (/\.jpg$/.test(event.request.url)) {
3 | event.respondWith(
4 | fetch('https://www.google.co.uk/logos/doodles/2014/60th-anniversary-of-the-unveiling-of-the-first-routemaster-bus-4922931108904960.3-hp.gif', {
5 | mode: 'no-cors'
6 | })
7 | );
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/src/demos/install-fail/error-thrown-oninstall/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
31 |
32 |
--------------------------------------------------------------------------------
/src/demos/install-fail/error-thrown-oninstall/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('install', function(event) {
2 | throw Error("This should fail the install");
3 | });
4 |
5 | self.addEventListener('fetch', function(event) {
6 | // we never get here
7 | event.respondWith(new Response("If you get this response, there's a bug"));
8 | });
9 |
--------------------------------------------------------------------------------
/src/demos/install-fail/execution-error/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
31 |
32 |
--------------------------------------------------------------------------------
/src/demos/install-fail/execution-error/sw.js:
--------------------------------------------------------------------------------
1 | throw Error("Faillllllll");
2 |
3 | self.addEventListener('fetch', function(event) {
4 | // we never get here
5 | event.respondWith(new Response("If you get this response, there's a bug"));
6 | });
7 |
--------------------------------------------------------------------------------
/src/demos/install-fail/rejected-promise/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
31 |
32 |
--------------------------------------------------------------------------------
/src/demos/install-fail/rejected-promise/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('install', function(event) {
2 | debugger;
3 | event.waitUntil(doSomeStuff());
4 | });
5 |
6 | self.addEventListener('fetch', function(event) {
7 | // we never get here
8 | event.respondWith(new Response("If you get this response, there's a bug"));
9 | });
10 |
11 | function doSomeStuff() {
12 | return new Promise(function(resolve) {
13 | setTimeout(resolve, 5000);
14 | }).then(function() {
15 | return functionDoesNotExist();
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/demos/installactivate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/src/demos/installactivate/sw.js:
--------------------------------------------------------------------------------
1 | console.log("SW startup");
2 |
3 | this.oninstall = function(event) {
4 | console.log("Install event", event);
5 | console.log(".replace", event.replace);
6 | console.log("self.skipWaiting", self.skipWaiting);
7 |
8 | if (event.waitUntil) {
9 | console.log("Testing waitUntil:");
10 | event.waitUntil(new Promise(function(resolve) {
11 | setTimeout(function() {
12 | console.log("This should appear before activate");
13 | resolve();
14 | }, 3000);
15 | }));
16 | }
17 | };
18 |
19 | this.onactivate = function(event) {
20 | console.log("Activate event", event);
21 | console.log(".waitUntil", event.waitUntil);
22 | };
23 |
--------------------------------------------------------------------------------
/src/demos/json-stream/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
22 | Streaming JSON demo
23 |
24 |
25 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/src/demos/manual-response/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
31 |
32 |
--------------------------------------------------------------------------------
/src/demos/manual-response/sw.js:
--------------------------------------------------------------------------------
1 | // The SW will be shutdown when not in use to save memory,
2 | // be aware that any global state is likely to disappear
3 | console.log("SW startup");
4 |
5 | self.addEventListener('install', function(event) {
6 | console.log("SW installed");
7 | });
8 |
9 | self.addEventListener('activate', function(event) {
10 | console.log("SW activated");
11 | });
12 |
13 | self.addEventListener('fetch', function(event) {
14 | console.log("Caught a fetch!");
15 | event.respondWith(new Response("Hello world!"));
16 | });
17 |
--------------------------------------------------------------------------------
/src/demos/nav-preload/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
3 | font-size: 14px;
4 | line-height: 1.5;
5 | color: #333;
6 | background-color: #fff;
7 | }
8 | .content {
9 | position: relative;
10 | margin: 1rem 0;
11 | }
12 | .actions li {
13 | margin: 1rem 0;
14 | }
15 | .actions form {
16 | display: inline;
17 | }
18 | .pl-c { color: rgb(150, 152, 150); }
19 | .pl-c1, .pl-s .pl-v { color: rgb(0, 134, 179); }
20 | .pl-e, .pl-en { color: rgb(121, 93, 163); }
21 | .pl-smi, .pl-s .pl-s1 { color: rgb(51, 51, 51); }
22 | .pl-k { color: rgb(167, 29, 93); }
23 | .pl-s, .pl-pds, .pl-s .pl-pse .pl-s1, .pl-sr, .pl-sr .pl-cce, .pl-sr .pl-sre, .pl-sr .pl-sra { color: rgb(24, 54, 145); }
24 | .pl-v { color: rgb(237, 106, 67); }
25 | .octicon { display: inline-block; vertical-align: text-top; fill: currentcolor; }
26 | a { background-color: transparent; }
27 | b, strong { font-weight: inherit; }
28 | b, strong { font-weight: bolder; }
29 | img { border-style: none; }
30 | svg:not(:root) { overflow: hidden; }
31 | code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; }
32 | hr { box-sizing: content-box; height: 0px; overflow: visible; }
33 | button, input, select, textarea { font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: inherit; font-family: inherit; margin: 0px; }
34 | button, input { overflow: visible; }
35 | button, select { text-transform: none; }
36 | button, html [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; }
37 | * { box-sizing: border-box; }
38 | input, select, textarea, button { font-family: inherit; font-size: inherit; line-height: inherit; }
39 | a { color: rgb(64, 120, 192); text-decoration: none; }
40 | strong { font-weight: 600; }
41 | hr, .rule { height: 0px; margin: 15px 0px; overflow: hidden; background: transparent; border-width: 0px 0px 1px; border-top-style: initial; border-right-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-left-color: initial; border-image: initial; border-bottom-style: solid; border-bottom-color: rgb(221, 221, 221); }
42 | hr::before, .rule::before { display: table; content: ""; }
43 | table { border-spacing: 0px; border-collapse: collapse; }
44 | td, th { padding: 0px; }
45 | button { cursor: pointer; }
46 | h1, h2, h3, h4, h5, h6 { margin-top: 0px; margin-bottom: 0px; }
47 | h2 { font-size: 24px; font-weight: 600; }
48 | h3 { font-size: 20px; font-weight: 600; }
49 | h4 { font-size: 16px; font-weight: 600; }
50 | h5 { font-size: 14px; font-weight: 600; }
51 | p { margin-top: 0px; margin-bottom: 10px; }
52 | blockquote { margin: 0px; }
53 | ul, ol { padding-left: 0px; margin-top: 0px; margin-bottom: 0px; }
54 | ol ol, ul ol { list-style-type: lower-roman; }
55 | tt, code { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; }
56 | pre { margin-top: 0px; margin-bottom: 0px; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; font-size: 12px; line-height: normal; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; }
57 | .octicon { vertical-align: text-bottom; }
58 | .btn { position: relative; display: inline-block; padding: 6px 12px; font-size: 14px; font-weight: 600; line-height: 20px; color: rgb(51, 51, 51); white-space: nowrap; vertical-align: middle; cursor: pointer; user-select: none; background-color: rgb(238, 238, 238); background-image: linear-gradient(rgb(252, 252, 252), rgb(238, 238, 238)); border: 1px solid rgb(213, 213, 213); border-radius: 3px; -webkit-appearance: none; }
59 | .btn-primary { color: rgb(255, 255, 255); text-shadow: rgba(0, 0, 0, 0.14902) 0px -1px 0px; background-color: rgb(108, 198, 68); background-image: linear-gradient(rgb(145, 221, 112), rgb(85, 174, 46)); border: 1px solid rgb(90, 173, 53); }
60 | .hidden-text-expander { display: block; }
61 | .hidden-text-expander.inline { position: relative; top: -1px; display: inline-block; margin-left: 5px; line-height: 0; }
62 | .hidden-text-expander a, .ellipsis-expander { display: inline-block; height: 12px; padding: 0px 5px 5px; font-size: 12px; font-weight: bold; line-height: 6px; color: rgb(85, 85, 85); text-decoration: none; vertical-align: middle; background: rgb(221, 221, 221); border: 0px; border-radius: 1px; }
63 | .btn-link { display: inline-block; padding: 0px; font-size: inherit; color: rgb(64, 120, 192); text-decoration: none; white-space: nowrap; cursor: pointer; user-select: none; background-color: transparent; border: 0px; -webkit-appearance: none; }
64 | .btn-link:disabled { color: rgb(118, 118, 118); pointer-events: none; cursor: default; }
65 | input, textarea { font-feature-settings: 'liga' 0; }
66 | .tooltipped { position: relative; }
67 | .tooltipped::after { position: absolute; z-index: 1000000; display: none; padding: 5px 8px; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; font-size: 11px; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; -webkit-font-smoothing: subpixel-antialiased; color: rgb(255, 255, 255); text-align: center; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-wrap: break-word; white-space: pre; pointer-events: none; content: attr(aria-label); background: rgba(0, 0, 0, 0.8); border-radius: 3px; opacity: 0; }
68 | .tooltipped::before { position: absolute; z-index: 1000001; display: none; width: 0px; height: 0px; color: rgba(0, 0, 0, 0.8); pointer-events: none; content: ""; border: 5px solid transparent; opacity: 0; }
69 | .tooltipped-s::before, .tooltipped-se::before, .tooltipped-sw::before { top: auto; right: 50%; bottom: -5px; margin-right: -5px; border-bottom-color: rgba(0, 0, 0, 0.8); }
70 | .tooltipped-se::after { right: auto; left: 50%; margin-left: -15px; }
71 | .tooltipped-n::before, .tooltipped-ne::before, .tooltipped-nw::before { top: -5px; right: 50%; bottom: auto; margin-right: -5px; border-top-color: rgba(0, 0, 0, 0.8); }
72 | .tooltipped-s::after, .tooltipped-n::after { transform: translateX(50%); }
73 | .tooltipped-multiline::after { width: max-content; max-width: 250px; word-break: break-word; word-wrap: normal; white-space: pre-line; border-collapse: separate; }
74 | .float-right { float: right !important; }
75 | .d-block { display: block !important; }
76 | .d-none { display: none !important; }
77 | .mr-1 { margin-right: 4px !important; }
78 | .text-bold { font-weight: 600 !important; }
79 | .avatar { display: inline-block; overflow: hidden; line-height: 1; vertical-align: middle; border-radius: 3px; }
80 | .avatar-small { border-radius: 2px; }
81 | .markdown-body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; line-height: 1.5; word-wrap: break-word; }
82 | .markdown-body::before { display: table; content: ""; }
83 | .markdown-body::after { display: table; clear: both; content: ""; }
84 | .markdown-body > :first-child { margin-top: 0px !important; }
85 | .markdown-body > :last-child { margin-bottom: 0px !important; }
86 | .markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre { margin-top: 0px; margin-bottom: 16px; }
87 | .markdown-body hr { height: 0.25em; padding: 0px; margin: 24px 0px; background-color: rgb(231, 231, 231); border: 0px; }
88 | .markdown-body blockquote { padding: 0px 1em; color: rgb(119, 119, 119); border-left: 0.25em solid rgb(221, 221, 221); }
89 | .markdown-body blockquote > :first-child { margin-top: 0px; }
90 | .markdown-body blockquote > :last-child { margin-bottom: 0px; }
91 | .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; }
92 | .markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { font-size: inherit; }
93 | .markdown-body h2 { padding-bottom: 0.3em; font-size: 1.5em; border-bottom: 1px solid rgb(238, 238, 238); }
94 | .markdown-body h3 { font-size: 1.25em; }
95 | .markdown-body h4 { font-size: 1em; }
96 | .markdown-body h5 { font-size: 0.875em; }
97 | .markdown-body ul, .markdown-body ol { padding-left: 2em; }
98 | .markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { margin-top: 0px; margin-bottom: 0px; }
99 | .markdown-body li + li { margin-top: 0.25em; }
100 | .markdown-body code, .markdown-body tt { padding: 0.2em 0px; margin: 0px; font-size: 85%; background-color: rgba(0, 0, 0, 0.0392157); border-radius: 3px; }
101 | .markdown-body code::before, .markdown-body code::after, .markdown-body tt::before, .markdown-body tt::after { letter-spacing: -0.2em; content: " "; }
102 | .markdown-body pre { word-wrap: normal; }
103 | .markdown-body pre > code { padding: 0px; margin: 0px; font-size: 100%; word-break: normal; white-space: pre; background: transparent; border: 0px; }
104 | .markdown-body .highlight { margin-bottom: 16px; }
105 | .markdown-body .highlight pre { margin-bottom: 0px; word-break: normal; }
106 | .markdown-body .highlight pre, .markdown-body pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: rgb(247, 247, 247); border-radius: 3px; }
107 | .markdown-body pre code, .markdown-body pre tt { display: inline; padding: 0px; margin: 0px; overflow: visible; line-height: inherit; word-wrap: normal; background-color: transparent; border: 0px; }
108 | .markdown-body pre code::before, .markdown-body pre code::after, .markdown-body pre tt::before, .markdown-body pre tt::after { content: normal; }
109 | .state { display: inline-block; padding: 4px 8px; font-weight: 600; line-height: 20px; color: rgb(255, 255, 255); text-align: center; background-color: rgb(153, 153, 153); border-radius: 3px; }
110 | .state-open, .state-proposed, .state-reopened { background-color: rgb(108, 198, 68); }
111 | .state-closed { background-color: rgb(189, 44, 0); }
112 | .comment-body { width: 100%; padding: 15px; overflow: visible; font-size: 14px; }
113 | .comment-body .highlight { background-color: transparent; overflow: visible !important; }
114 | .commit-desc { display: none; }
115 | .commit-desc pre { max-width: 700px; margin-top: 10px; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 11px; line-height: 1.45; color: rgb(89, 96, 99); white-space: pre-wrap; }
116 | .timeline-commits { width: 100%; margin-top: 5px; border-collapse: separate; }
117 | .timeline-commits td { padding-top: 4px; padding-right: 8px; padding-bottom: 4px; font-size: 12px; line-height: 16px; vertical-align: top; background-color: transparent; }
118 | .discussion-item .timeline-commits .commit-author { display: none; }
119 | .timeline-commits .commit-gravatar { width: 16px; padding-left: 10px; }
120 | .timeline-commits .commit-author { width: 200px; padding-right: 20px; white-space: nowrap; }
121 | .timeline-commits .author { font-weight: bold; color: rgb(85, 85, 85); }
122 | .timeline-commits .commit-message { max-width: 550px; min-height: 0px; }
123 | .timeline-commits .commit-message > code a { color: rgb(85, 85, 85); }
124 | .timeline-commits .commit-desc pre { overflow: visible; color: rgb(118, 118, 118); }
125 | .timeline-commits .hidden-text-expander { margin-top: 3px; margin-left: 0px; vertical-align: top; }
126 | .timeline-commits .hidden-text-expander .ellipsis-expander { height: 13px; background-color: rgb(238, 238, 238); }
127 | .timeline-commits .commit-sig-status { width: 60px; padding-right: 4px; text-align: right; }
128 | .timeline-commits .commit-ci-status { width: 16px; padding-right: 4px; }
129 | .timeline-commits .commit-meta { width: 50px; text-align: right; }
130 | .commit-icon { display: table-cell; width: 16px; color: rgb(204, 204, 204); }
131 | .commit-icon .octicon { background-color: rgb(255, 255, 255); }
132 | .commit-id { color: rgb(187, 187, 187); }
133 | .discussion-timeline::before { position: absolute; top: 0px; bottom: 0px; left: 79px; z-index: -1; display: block; width: 2px; content: ""; background-color: rgb(243, 243, 243); }
134 | .timeline-comment-wrapper > .timeline-comment::after, .timeline-comment-wrapper > .timeline-comment::before, .timeline-new-comment .timeline-comment::after, .timeline-new-comment .timeline-comment::before { position: absolute; top: 11px; right: 100%; left: -16px; display: block; width: 0px; height: 0px; pointer-events: none; content: " "; border-color: transparent; border-style: solid solid outset; }
135 | .timeline-comment-wrapper > .timeline-comment::before, .timeline-new-comment .timeline-comment::before { border-width: 8px; border-right-color: rgb(221, 221, 221); }
136 | .timeline-comment-wrapper { position: relative; padding-left: 60px; margin-top: 15px; margin-bottom: 15px; border-top: 2px solid rgb(255, 255, 255); border-bottom: 2px solid rgb(255, 255, 255); }
137 | .timeline-comment-wrapper:first-child { margin-top: 0px; }
138 | .timeline-comment-avatar { float: left; margin-left: -60px; border-radius: 3px; }
139 | .timeline-comment { position: relative; background-color: rgb(255, 255, 255); border: 1px solid rgb(221, 221, 221); border-radius: 3px; }
140 | .timeline-comment-header { padding-right: 15px; padding-left: 15px; color: rgb(118, 118, 118); background-color: rgb(247, 247, 247); border-bottom: 1px solid rgb(221, 221, 221); border-top-left-radius: 3px; border-top-right-radius: 3px; }
141 | .timeline-comment-header .author { color: rgb(85, 85, 85); }
142 | .timeline-comment-header .timestamp { color: inherit; white-space: nowrap; }
143 | .timeline-comment-header .timestamp.timestamp-edited { cursor: default; }
144 | .timeline-comment-label { float: right; padding: 2px 5px; margin: 8px 0px 0px 10px; font-size: 12px; cursor: default; border: 1px solid rgba(0, 0, 0, 0.0980392); border-radius: 3px; }
145 | .timeline-comment-header-text { max-width: 78%; padding-top: 10px; padding-bottom: 10px; }
146 | .timeline-comment-actions { float: right; margin-right: -5px; margin-left: 10px; }
147 | .discussion-item-ref .commit-gravatar { padding-right: 5px; padding-left: 2px; }
148 | .discussion-item-ref .state { padding: 1px 5px; margin-left: 8px; font-size: 12px; }
149 | .discussion-item-ref .state .octicon { width: 1em; font-size: 14px; }
150 | .discussion-item + .discussion-item, .discussion-item-review + .discussion-item { padding-top: 15px; border-top: 1px solid rgb(245, 245, 245); }
151 | .discussion-item { position: relative; padding-left: 25px; margin: 15px 0px 15px 79px; }
152 | .discussion-item .author { font-weight: 600; color: rgb(85, 85, 85); }
153 | .discussion-item .timestamp { color: inherit; white-space: nowrap; }
154 | .discussion-item-icon { float: left; width: 32px; height: 32px; margin-top: -7px; margin-left: -40px; line-height: 28px; color: rgb(118, 118, 118); text-align: center; background-color: rgb(243, 243, 243); border: 2px solid rgb(255, 255, 255); border-radius: 50%; }
155 | .discussion-item-icon .octicon-pencil { font-size: 14px; }
156 | .discussion-item-header { min-height: 30px; padding-top: 5px; padding-bottom: 5px; line-height: 20px; color: rgb(118, 118, 118); word-wrap: break-word; }
157 | .discussion-item-header .avatar { width: 16px; height: 16px; }
158 | .discussion-item-header:last-child { padding-bottom: 0px; }
159 | .discussion-item-entity { font-weight: 600; color: rgb(51, 51, 51); }
160 | .discussion-item-ref-title .issue-num { font-weight: normal; color: rgb(118, 118, 118); }
161 | .discussion-item-ref-title .title-link { color: rgb(51, 51, 51); }
162 | .discussion-item-rollup-ref .state { margin-top: 2px; }
163 | .discussion-item .renamed-was, .discussion-item .renamed-is { font-weight: bold; color: rgb(51, 51, 51); }
164 | .discussion-timeline-actions { background-color: rgb(255, 255, 255); border-top: 2px solid rgb(243, 243, 243); }
165 | g-emoji { font-family: "Apple Color Emoji", "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 18px; font-weight: normal; line-height: 20px; vertical-align: middle; }
166 | html.emoji-size-boost g-emoji { margin-right: 3px; }
167 | .user-mention, .team-mention { font-weight: 600; color: rgb(51, 51, 51); white-space: nowrap; }
168 | .comment-reactions::before { display: table; content: ""; }
169 | .comment-reactions::after { display: table; clear: both; content: ""; }
170 | .comment-reactions.has-reactions { border-top: 1px solid rgb(229, 229, 229); }
171 | .reaction-summary-item { float: left; padding: 9px 15px 7px; line-height: 18px; border-right: 1px solid rgb(229, 229, 229); }
172 | .comment-reactions-options .reaction-summary-item:first-child { border-bottom-left-radius: 2px; }
173 | .signed-out-comment { padding: 15px; margin-top: 15px; margin-left: 64px; background-color: rgb(255, 249, 234); border: 1px solid rgb(223, 216, 194); border-radius: 3px; }
174 | .signed-out-comment .btn { margin-right: 3px; vertical-align: baseline; }
175 | hr { border-bottom-color: rgb(238, 238, 238); }
176 | .btn-link { font-family: inherit; }
177 | .text-bold { font-weight: 500 !important; }
--------------------------------------------------------------------------------
/src/demos/nav-preload/sw.js:
--------------------------------------------------------------------------------
1 | // Slow the serviceworker down a bit
2 | const start = Date.now();
3 | while (Date.now() - start < 500);
4 |
5 | addEventListener('install', event => {
6 | event.waitUntil(async function() {
7 | const cache = await caches.open('nav-preload-demo-v1');
8 | await cache.add('styles.css');
9 | }());
10 | });
11 |
12 | addEventListener('activate', event => {
13 | event.waitUntil(async function() {
14 | if (self.registration.navigationPreload) {
15 | await self.registration.navigationPreload.enable();
16 | }
17 | }());
18 | });
19 |
20 | addEventListener('fetch', event => {
21 | event.respondWith(async function() {
22 | // Respond from the cache if we can
23 | const cachedResponse = await caches.match(event.request);
24 | if (cachedResponse) return cachedResponse;
25 |
26 | // Use the preloaded response, if it's there
27 | const response = await event.preloadResponse;
28 | if (response) return response;
29 |
30 | // Else try the network.
31 | return fetch(event.request);
32 | }());
33 | });
--------------------------------------------------------------------------------
/src/demos/navigator.serviceWorker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/demos/page-cache-bug/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 | Clear caches
17 | Cache image
18 | Get image from cache, and put in another cache
19 |
20 |
89 |
90 |
--------------------------------------------------------------------------------
/src/demos/page-cache-bug/sw.js:
--------------------------------------------------------------------------------
1 | self.oninstall = function() {
2 | self.skipWaiting();
3 | };
4 |
5 | self.onactivate = function() {
6 | clients.claim();
7 | };
8 |
9 |
10 | self.onfetch = function(event) {
11 | event.respondWith(
12 | caches.match(event.request).then(function(response) {
13 | return response || fetch(event.request);
14 | })
15 | );
16 | };
--------------------------------------------------------------------------------
/src/demos/postMessage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
59 |
60 |
--------------------------------------------------------------------------------
/src/demos/postMessage/sw.js:
--------------------------------------------------------------------------------
1 | this.onmessage = function(event) {
2 | console.log("Got message in SW", event.data.text);
3 |
4 | if (event.source) {
5 | console.log("event.source present");
6 | event.source.postMessage("Woop!");
7 | }
8 | else if (self.clients) {
9 | console.log("Attempting postMessage via clients API");
10 | clients.matchAll().then(function(clients) {
11 | for (var client of clients) {
12 | client.postMessage("Whoop! (via client api)");
13 | }
14 | });
15 | }
16 | else if (event.data.port) {
17 | event.data.port.postMessage("Woop!");
18 | }
19 | else {
20 | console.log('No useful return channel');
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/src/demos/redirect/destination/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/demos/redirect/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 | Click me!
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/demos/redirect/sw.js:
--------------------------------------------------------------------------------
1 | self.onfetch = function(event) {
2 | event.respondWith(fetch(event.request));
3 | };
--------------------------------------------------------------------------------
/src/demos/registerunregister/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
46 |
47 |
--------------------------------------------------------------------------------
/src/demos/registerunregister/sw.js:
--------------------------------------------------------------------------------
1 | console.log("SW startup");
2 |
--------------------------------------------------------------------------------
/src/demos/scope/app-a/index.html:
--------------------------------------------------------------------------------
1 |
2 | Scope demo
3 |
8 | App A
9 | back
--------------------------------------------------------------------------------
/src/demos/scope/app-a/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('fetch', event => {
2 | console.log('Intercepted fetch for', event.request.url);
3 | console.log('Intercepted by', location.href, 'with scope', registration.scope);
4 | });
--------------------------------------------------------------------------------
/src/demos/scope/app-b-sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('fetch', event => {
2 | console.log('Intercepted fetch for', event.request.url);
3 | console.log('Intercepted by', location.href, 'with scope', registration.scope);
4 | });
--------------------------------------------------------------------------------
/src/demos/scope/app-b/index.html:
--------------------------------------------------------------------------------
1 |
2 | Scope demo
3 |
8 | App B
9 | back
--------------------------------------------------------------------------------
/src/demos/scope/index.html:
--------------------------------------------------------------------------------
1 |
2 | Scope demo
3 |
8 |
13 | Managing multiple scopes
14 |
15 | This page registers 3 sevice workers:
16 |
17 |
18 | ./sw.js default-scoped to ./
19 | ./app-a/sw.js default-scoped to ./app-a/
20 | ./app-b-sw.js scoped to ./app-b (note the lack of trailing slash)
21 |
22 |
23 | Usually these would be registered by each individual app, but I've done it all
24 | on this page so they're all registered by the time you visit the following pages.
25 |
26 |
32 |
33 | Each service worker logs when it intercepts a fetch, use "preserve log" in the console
34 | to see them all.
35 |
36 | What you should see:
37 |
38 | Navigations to this page are intercepted by ./sw.js.
39 | Navigations to app-a/ are intercepted by app-a/sw.js.
40 | Navigations to app-a are intercepted by ./sw.js, the server redirects to app-a/ which is intercepted by app-a/sw.js.
41 | Navigations to app-b are intercepted by ./app-b-sw.js, the server redirects to app-b/ which is also intercepted by ./app-b-sw.js.
42 |
--------------------------------------------------------------------------------
/src/demos/scope/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('fetch', event => {
2 | console.log('Intercepted fetch for', event.request.url);
3 | console.log('Intercepted by', location.href, 'with scope', registration.scope);
4 | });
--------------------------------------------------------------------------------
/src/demos/simple-stream/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 | Loading service worker…
13 |
14 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/demos/simple-stream/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('install', event => {
2 | self.skipWaiting();
3 | });
4 |
5 | self.addEventListener('activate', event => {
6 | clients.claim();
7 | });
8 |
9 | self.addEventListener('fetch', event => {
10 | const requestURL = new URL(event.request.url);
11 |
12 | if (requestURL.origin != location.origin) return;
13 |
14 | if (requestURL.pathname.endsWith("/demos/simple-stream/")) {
15 | event.respondWith(umpaLumpaStream());
16 | }
17 | });
18 |
19 | function retab(str) {
20 | // remove blank lines
21 | str = str.replace(/^\s*\n|\n\s*$/g, '');
22 | const firstIndent = /^\s*/.exec(str)[0];
23 | return str.replace(RegExp('^' + firstIndent, 'mg'), '');
24 | }
25 |
26 | function umpaLumpaStream() {
27 | const html = retab(`
28 |
29 |
34 |
35 | The Oompa Loompa song
36 |
37 |
38 | Oompa loompa doompety doo
39 | I've got a perfect puzzle for you
40 | Oompa loompa doompety dee
41 | If you are wise you'll listen to me
42 |
43 |
44 |
45 | What do you get when you guzzle down sweets
46 | Eating as much as an elephant eats
47 | What are you at, getting terribly fat
48 | What do you think will come of that
49 | I don't like the look of it
50 |
51 |
52 |
53 | Oompa loompa doompety da
54 | If you're not greedy, you will go far
55 | You will live in happiness too
56 | Like the Oompa Loompa Doompety do
57 | Doompety do
58 |
59 |
60 |
61 | Oompa loompa doompety doo
62 | I've got another puzzle for you
63 | Oompa loompa doompeda dee
64 | If you are wise you'll listen to me
65 |
66 |
67 |
68 | Gum chewing's fine when it's once in a while
69 | It stops you from smoking and brightens your smile
70 | But it's repulsive, revolting and wrong
71 | Chewing and chewing all day long
72 | The way that a cow does
73 |
74 |
75 |
76 | Oompa loompa doompety da
77 | Given good manners you will go far
78 | You will live in happiness too
79 | Like the Oompa Loompa Doompety do
80 |
81 |
82 |
83 | Oompa loompa doompety doo
84 | I've got another puzzle for you
85 | Oompa loompa doompety dee
86 | If you are wise you'll listen to me
87 |
88 |
89 |
90 | Who do you blame when your kid is a brat
91 | Pampered and spoiled like a siamese cat
92 | Blaming the kids is a lie and a shame
93 | You know exactly who's to blame
94 | The mother and the father
95 |
96 |
97 |
98 | Oompa loompa doompety da
99 | If you're not spoiled then you will go far
100 | You will live in happiness too
101 | Like the Oompa Loompa Doompety do
102 |
103 |
104 |
105 | Oompa loompa doompety doo
106 | I've got another puzzle for you
107 | Oompa loompa doompeda dee
108 | If you are wise you'll listen to me
109 |
110 |
111 |
112 | What do you get from a glut of TV
113 | A pain in the neck and an IQ of three
114 | Why don't you try simply reading a book
115 | Or could you just not bear to look
116 | You'll get no
117 | You'll get no
118 | You'll get no
119 | You'll get no
120 | You'll get no commercials
121 |
122 |
123 |
124 | Oompa loompa doompety da
125 | If you're not greedy you will go far
126 | You will live in happiness too
127 | Like the - Oompa -
128 | Oompa Loompa Doompety do
129 |
130 | `);
131 |
132 | const stream = new ReadableStream({
133 | start: controller => {
134 | const encoder = new TextEncoder();
135 | let pos = 0;
136 | let chunkSize = 1;
137 |
138 | function push() {
139 | if (pos >= html.length) {
140 | controller.close();
141 | return;
142 | }
143 |
144 | controller.enqueue(
145 | encoder.encode(html.slice(pos, pos + chunkSize))
146 | );
147 |
148 | pos += chunkSize;
149 | setTimeout(push, 5);
150 | }
151 |
152 | push();
153 | }
154 | });
155 |
156 | return new Response(stream, {
157 | headers: {
158 | 'Content-Type': 'text/html'
159 | }
160 | });
161 | }
--------------------------------------------------------------------------------
/src/demos/slow-update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
31 |
32 |
--------------------------------------------------------------------------------
/src/demos/slow-update/sw.js:
--------------------------------------------------------------------------------
1 | function wait(ms) {
2 | return new Promise(function(resolve) {
3 | setTimeout(resolve, ms);
4 | });
5 | }
6 |
7 | self.addEventListener('install', function(event) {
8 | console.log("Installing…");
9 | event.waitUntil(
10 | wait(5000).then(function() {
11 | console.log("Installed!");
12 | })
13 | );
14 | });
15 |
16 | self.addEventListener('activate', function(event) {
17 | console.log("Activating…");
18 | event.waitUntil(
19 | wait(5000).then(function() {
20 | console.log("Activated!");
21 | })
22 | );
23 | });
24 |
25 | self.addEventListener('fetch', function(event) {
26 | event.respondWith(new Response("Hello everyone!"));
27 | });
28 |
--------------------------------------------------------------------------------
/src/demos/sync/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 | One-off background sync doesn't require permissions, but notifications do,
14 | and that's how we're going to tell you it worked.
15 |
16 | Registering from the page
17 | Register background sync
18 |
19 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/demos/sync/sw.js:
--------------------------------------------------------------------------------
1 | self.addEventListener('install', function(event) {
2 | self.skipWaiting();
3 | });
4 |
5 | self.addEventListener('sync', function(event) {
6 | self.registration.showNotification("Sync event fired!");
7 | });
8 |
--------------------------------------------------------------------------------
/src/demos/template-stream/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 | Loading service worker…
13 |
14 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/demos/template-stream/prism.js:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=clike+javascript */
2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(m instanceof a)){u.lastIndex=0;var y=u.exec(m),v=1;if(!y&&h&&p!=r.length-1){var b=r[p+1].matchedStr||r[p+1],k=m+b;if(p=m.length)continue;var _=y.index+y[0].length,P=m.length+b.length;if(v=3,P>=_){if(r[p+1].greedy)continue;v=2,k=k.slice(0,P)}m=k}if(y){g&&(f=y[1].length);var w=y.index+f,y=y[0].slice(f),_=w+y.length,S=m.slice(0,w),O=m.slice(_),j=[p,v];S&&j.push(S);var A=new a(i,c?n.tokenize(y,c):y,d,y,h);j.push(A),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+""+l.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",n.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
3 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
4 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(
27 |
28 |
29 | This content is streamed from the service worker
30 | For instance, this image tag is populated from a request to Flickr's API:
31 |
32 | The final word in this paragraph is artificially ${slowContent} .
33 | And just to be really meta, here's the service worker that created this streaming response also streamed into this response:
34 | ${serviceWorkerScript}
35 |
36 |