soiz1 commited on
Commit
8f3f8db
·
verified ·
1 Parent(s): b3de7b3

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.gitattributes CHANGED
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ benchmark/benchmark.image filter=lfs diff=lfs merge=lfs -text
37
+ demo/mini.image filter=lfs diff=lfs merge=lfs -text
38
+ demo/squeakjs.image filter=lfs diff=lfs merge=lfs -text
39
+ headless/headless.image filter=lfs diff=lfs merge=lfs -text
40
+ ws/client/cuis.image filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .DS_Store
2
+ node_modules/
3
+ gh-pages
4
+ dist/*.min.js*
5
+ *.code-workspace
.gitpod.yml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ tasks:
2
+ - init: nvm install && npm install && npm run build
.nvmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ v22.14.0
.travis.yml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language: node_js
2
+ node_js:
3
+ - "6"
4
+ before_install:
5
+ - export CHROME_URL=https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64
6
+ - export CHROME_REV=$(curl -s ${CHROME_URL}/LAST_CHANGE)
7
+ - curl ${CHROME_URL}/${CHROME_REV}/chrome-linux.zip --create-dirs -o out/chrome-linux.zip
8
+ - unzip -q out/chrome-linux.zip -d out
9
+ - export CHROME_CANARY_BIN=$PWD/out/chrome-linux/chrome
10
+ - export DISPLAY=:99.0
11
+ - sh -e /etc/init.d/xvfb start
12
+ install:
13
+ - cd tests
14
+ - npm install
15
+ before_script:
16
+ - curl -f -s -L --retry 3 -o Squeak-5.1.tgz https://dl.bintray.com/squeakjs/testing/Squeak-5.1.tgz
17
+ - tar xzf Squeak-5.1.tgz -C ./resources
18
+ script: npm test
19
+ cache:
20
+ directories:
21
+ - tests/node_modules
LICENSE.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013-2025 Vanessa Freudenberg
4
+ Copyright (c) 2016 Fabio Niephaus, Google Inc.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
README.md CHANGED
@@ -1,10 +1,229 @@
1
- ---
2
- title: Scratch0 5
3
- emoji: 🦀
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: static
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SqueakJS: A Squeak VM for the Web and Node.js
2
+ =============================================
3
+
4
+ SqueakJS is a runtime engine for [Squeak][squeak]</a> Smalltalk written in pure JavaScript. It also works for many other OpenSmalltalk-compatible images.
5
+
6
+ Embedding a Smalltalk application in your webpage can be as simple as:
7
+
8
+ SqueakJS.runSqueak(imageUrl);
9
+
10
+ but you probably want to give it some more options (refer to the examples).
11
+
12
+ The interpreter core is divided in a number of `vm.*.js` modules, internal plugins in `vm.plugins.*.js` modules and external plugins in the "plugins" directory. The Just-in-Time compiler is optional ("jit.js") and can be replaced with your own.
13
+
14
+ There are a number of interfaces:
15
+ * browser: the regular HTML interface lets you use SqueakJS on your own web page. Just include "squeak.js".
16
+ * headless browser: a headless VM. It lets you use SqueakJS in your browser without a direct UI (you can create your own UI with a plugin). Include "squeak_headless.js" and add an "imageName" parameter to your website URL (eg. https://example.com/my/page.html?imageName=./example.image) or call the Javascript function "fetchImageAndRun('https://example.com/my/example.image')" to start the specified image.
17
+ * Node.js: another headless VM. It lets you use SqueakJS as a Node.js application via "node squeak_node.js <image name>".
18
+
19
+ For discussions, please use the [vm-dev mailing list][vm-dev]. Also, please visit the [project home page][homepage]!
20
+
21
+ Running it
22
+ ----------
23
+ **Simplest**
24
+
25
+ * [Run a minimal image][mini]. This is the simple demo included in this repo.
26
+ * Or run [Etoys][etoys]. Everything except the image and template files is in this repo.
27
+ * Or similarly, [Scratch][scratch], also in here.
28
+
29
+ **Run your own Squeak image in the browser**
30
+
31
+ * Drag an image from your local files into the [launcher][run].
32
+ * ... and all the other demo pages (see above) accept dropped images, too.
33
+
34
+ **Run your own Squeak image from the command line**
35
+
36
+ * Install a recent version of Node.js
37
+ * Run example image: `node squeak_node.js headless/headless.image`
38
+
39
+ **Run an interactive shell based on WebSocket communication with Cuis image**
40
+
41
+ * Install a recent version of Node.js
42
+ * Go to [ws][ws] and execute `start_server.sh` in a first shell and `start_client.sh` in a second shell.
43
+ * After initialization it should be possible to issue Smalltalk statements which will be executed in the Smalltalk image.
44
+ * Try commands like: `Object allSubclasses size` `1837468731248764723 * 321653125376153761` `Collection allSubclasses collect: [ :c | c name ]`
45
+
46
+ **Which Browser**
47
+
48
+ All modern desktop browsers should work. Mobile browsers work too, but most Squeak images assume a keyboard and mouse. YMMV.
49
+
50
+ Fixes to improve browser compatibility are highly welcome!
51
+
52
+ If your browser does not support ES6 modules try the full or headless SqueakJS VM as a single file (aka bundle) in the [Distribution][dist] directory.
53
+
54
+
55
+ Installing locally
56
+ ------------------
57
+ * clone the [github repo][repo]:
58
+ ```
59
+ git clone https://github.com/codefrau/SqueakJS.git
60
+ ```
61
+ or download and unpack the [ZIP archive][zip]
62
+ * serve the SqueakJS directory using a local web server.
63
+
64
+ TIP: If you have Node.js, try
65
+ ```
66
+ cd SqueakJS
67
+ npx serve
68
+ ```
69
+ which will run a webserver on port 3000.
70
+ * in a web browser, open http://localhost:3000/run/ and pick one of the images, or drag and drop your own
71
+
72
+ Now Squeak should be running.
73
+ The reason for having to run from a web server is because the image is loaded with an XMLHttpRequest which does not work with a file URL. Alternatively, you could just open SqueakJS/run/index.html and drop in a local image.
74
+
75
+ Using (self contained) bundled files
76
+ ------------------------------------
77
+ * select your preferred type of interface (browser or headless)
78
+ * use the appropriate file (`squeak_bundle.js` resp. `squeak_headless_bundle.js`) from the [Distribution][dist] directory
79
+ * you can also build minified bundles using `npm run build`
80
+
81
+ How to modify it
82
+ ----------------
83
+ * use any text editor
84
+ * you have to reload the page for your changes to take effect
85
+
86
+ How to share your changes
87
+ -------------------------
88
+ * easiest for me is if you create a [pull request][pullreq]
89
+ * otherwise, send me patches
90
+
91
+ Contributions are very welcome!
92
+
93
+ Things to work on
94
+ -----------------
95
+ SqueakJS is intended to run any Squeak image. It can already load any image from the original 1996 Squeak release to the latest Cog-Spur release, including 64-bit and Sista variants. But various pieces (primitives in various plugins) are still missing, in particular 3D graphics and networking (however, see [Croquet][jasmine] which supports both, but should be generalized). Also, we should make pre-Spur 64 bit images load. And, it would be nice to make it work on as many browsers as possible, especially on mobile touch devices.
96
+
97
+ As for optimizing the way to go is an optimizing JIT compiler. The current JIT is very simple and does not optimize at all, it only eloiminates the interpreter's instruction decoding overhead. Since we can't access or manipulate the JavaScript stack, we might want that compiler to inline as much as possible, but keep the call sequence flat so we can return to the browser at any time. Even better (but potentially more complicated) is actually using the JavaScript stack, just like Eliot's Stack VM uses the C stack. I have done some [advanced JIT mockups][jit]. To make BitBlt fast, we could probably use WASM or even WebGL.
98
+
99
+ To make SqueakJS useful beyond running existing Squeak images, we should use the JavaScript bridge to write a native HTML UI which would certainly be much faster than BitBlt (Craig Latta has done some interesting work towards that in [Caffeine][caffeine]).
100
+
101
+ Better Networking would be interesting, too. The SocketPlugin currently does allows HTTP(S) requests and WebSockets. How about implementing low level Socket support based on HTTP-tunneling? The VM can run in a WebWorker. How about parallelizing the VM with WebWorkers?
102
+
103
+ Also interesting would be wrapping it in a native app, maybe via [Electron][electron] similar to [Sugarizer][sugarizer], which uses SqueakJS to run Etoys.
104
+
105
+ There's a gazillion exciting things to do :)
106
+
107
+ -- Vanessa Freudenberg (codefrau)
108
+
109
+ [squeak]: https://squeak.org/
110
+ [repo]: https://github.com/codefrau/SqueakJS
111
+ [vm-dev]: http://lists.squeakfoundation.org/mailman/listinfo/vm-dev
112
+ [homepage]: https://squeak.js.org/
113
+ [run]: https://squeak.js.org/run/
114
+ [mini]: https://squeak.js.org/demo/simple.html
115
+ [etoys]: https://squeak.js.org/etoys/
116
+ [scratch]: https://squeak.js.org/scratch/
117
+ [jasmine]: https://github.com/codefrau/jasmine
118
+ [caffeine]: https://caffeine.js.org/
119
+ [jit]: https://squeak.js.org/docs/jit.md.html
120
+ [ws]: https://github.com/codefrau/SqueakJS/tree/main/ws
121
+ [dist]: https://github.com/codefrau/SqueakJS/tree/main/dist
122
+ [zip]: https://github.com/codefrau/SqueakJS/archive/main.zip
123
+ [pullreq]: https://help.github.com/articles/using-pull-requests
124
+ [electron]: https://www.electronjs.org
125
+ [sugarizer]: https://github.com/llaske/sugarizer
126
+
127
+
128
+ Changelog
129
+ ---------
130
+ 2025-05-04: 1.3.3 minor FFI, OpenGL, and other fixes/improvements
131
+ 2025-04-06: 1.3.2 use our own CORS proxy, add welcome=false option, minor fixes
132
+ 2025-03-29: 1.3.1 add 'w', 'h', 'embedded' canvas options, minor fixes
133
+ 2025-03-28: 1.3.0 add OpenGL support, canvas is optional, fix socket plugin bug
134
+ 2025-02-19: 1.2.4 fix isAssociation for JS Bridge, optimize loading image with many objects
135
+ 2024-09-28: 1.2.3 fix primitiveInputSemaphore, fix iOS keyboard
136
+ 2024-06-22: 1.2.2 make copy/paste work on mobile
137
+ 2024-05-27: 1.2.1 add virtual cmd button, fix touch events
138
+ 2024-03-25: 1.2.0 add FFI and MIDI plugins, JIT for Sista bytecodes, JPEG write prim, fix keyboard input, copy/paste, scroll wheel, highdpi, allow ES6 in source
139
+ 2023-11-24: 1.1.2 fixed BitBlt bug (symptom reported 9 years ago, thanks to Agustin Martinez for narrowing it down), add object pinning, support keyboard in ancient Scratch images
140
+ 2023-10-24: 1.1.1 workarounds for Cuis 6
141
+ 2023-10-23: 1.1.0 implement Etoys project saving (image segment export), drag-n-drop directories
142
+ 2023-09-30: 1.0.6 fixes
143
+ 2022-11-19: 1.0.5 fixes, add highdpi mode, add image format for Squeak 6
144
+ 2021-05-31: 1.0.4 fixes
145
+ 2021-03-21: 1.0.3 headless fixes (Erik Stel); fixes object-as-method
146
+ 2021-02-07: 1.0.2 new one-way become prim (Christoph Tiede); JIT-compile Array at:/at:put:
147
+ 2021-01-05: 1.0.1 fixes some primitives to properly pop the stack
148
+ 2020-12-20: 1.0 supports 64 bits and Sista
149
+ 2020-06-20: renamed "master" branch to "main"
150
+ 2020-06-20: 0.9.9 JSBridge additions (Bill Burdick), fixes
151
+ 2020-04-08: renamed github account to "codefrau"
152
+ 2020-01-26: 0.9.8 split into modules (Erik Stel), fixes
153
+ 2019-01-03: 0.9.7 minor fixes
154
+ 2018-03-13: 0.9.6 minor fixes
155
+ 2016-11-08: 0.9.5 more fixes
156
+ 2016-10-20: 0.9.4 fixes
157
+ 2016-09-08: 0.9.3 add partial GC (5x faster become / allInstances)
158
+ 2016-08-25: 0.9.2 add keyboard on iOS
159
+ 2016-08-03: 0.9.1 fixes
160
+ 2016-07-29: 0.9 Spur support, stdout, SpeechPlugin, zipped images
161
+ 2016-06-28: 0.8.3 add SocketPlugin for http/https connections
162
+ 2016-04-07: 0.8.2 better touch handling, debugging, CORS, lint
163
+ 2016-01-08: 0.8.1 windows keyboard fixes, 'new' operator fixed
164
+ 2015-11-24: 0.8 minor fixes
165
+ 2015-08-13: 0.7.9 make work on iOS again
166
+ 2015-07-18: 0.7.8 fix keyboard
167
+ 2015-06-09: 0.7.7 fix thisContext
168
+ 2015-04-27: 0.7.6 revert JIT, minor fixes
169
+ 2015-04-14: 0.7.5 JIT optimizations by HPI students (reverted in 0.7.6)
170
+ 2015-02-18: 0.7.4 make pre-release image work
171
+ 2015-01-30: 0.7.3 JSBridge: fix closure callbacks
172
+ 2015-01-25: 0.7.2 JSBridge: add asJSObject
173
+ 2014-12-22: 0.7.1 cursor shapes
174
+ 2014-12-04: 0.7 support finalization of weak references
175
+ 2014-11-28: 0.6.8 JSBridge with callbacks
176
+ 2014-11-20: 0.6.7 implement JavaScriptPlugin
177
+ 2014-11-18: 0.6.6 implement DropPlugin
178
+ 2014-11-14: 0.6.5 add generated Balloon2D plugin
179
+ 2014-11-06: 0.6.4 add generic run page
180
+ 2014-10-28: 0.6.3 pass options via URL
181
+ 2014-10-27: add JPEG plugin
182
+ 2014-10-25: add template files
183
+ 2014-10-23: 0.6.2 fixes
184
+ 2014-10-21: 0.6.1 add image segment loading
185
+ 2014-10-18: 0.6 move squeak.js out of lib dir
186
+ 2014-10-13: 0.5.9 microphone support
187
+ 2014-10-09: 0.5.8 fixes
188
+ 2014-10-07: 0.5.7 even more plugins generated
189
+ 2014-10-07: 0.5.6 add quitSqueak and onQuit
190
+ 2014-10-07: 0.5.5 generated ScratchPlugin
191
+ 2014-10-06: 0.5.4 replace BitBltPlugin by generated
192
+ 2014-10-06: 0.5.3 SoundGenerationPlugin, Matrix2x3Plugin, FloatArrayPlugin
193
+ 2014-10-05: ZipPlugin
194
+ 2014-10-04: MiscPrimitivePlugin
195
+ 2014-10-03: VMMakerJS generates LargeIntegersPlugin
196
+ 2014-09-30: 0.5.2 more JIT
197
+ 2014-09-28: 0.5.1 JIT fixes
198
+ 2014-09-26: 0.5 add JIT compiler
199
+ 2014-09-22: v8 optimizations
200
+ 2014-09-20: 0.4.6 sound output support
201
+ 2014-09-13: 0.4.5 clipboartd fixes
202
+ 2014-09-12: 0.4.4 cut/copy/paste in stand-alone
203
+ 2014-09-09: 0.4.3 some scratch prims
204
+ 2014-09-09: 0.4.2 idle fixes
205
+ 2014-09-05: 0.4.1 scratch fixes
206
+ 2014-09-04: 0.4.0 runs scratch
207
+ 2014-08-31: switch old/new primitives
208
+ 2014-08-27: event-based input
209
+ 2014-08-21: exception handling
210
+ 2014-07-25: 0.3.3 fullscreen support
211
+ 2014-07-18: 0.3.2 benchmarking (timfel)
212
+ 2014-07-18: 0.3.1 deferred display
213
+ 2014-07-16: 0.3.0 closure support
214
+ 2014-07-14: 0.2.3 IE optimization (timfel)
215
+ 2014-07-11: 0.2.2 drag-n-drop
216
+ 2014-07-07: 0.2.1 fixes for IE11 (timfel)
217
+ 2014-07-04: 0.2 runs Etoys
218
+ 2014-06-27: Balloon2D (krono)
219
+ 2014-06-03: stand-alone version
220
+ 2014-05-29: 0.1 added version number
221
+ 2014-05-27: WarpBlt
222
+ 2014-05-07: image saving
223
+ 2014-04-23: file support
224
+ 2013-12-20: public release
225
+ 2013-12-14: colored bitblt
226
+ 2013-12-03: first pixels on screen
227
+ 2013-11-29: GC
228
+ 2013-11-22: runs 43 byte codes and 8 sends successfully
229
+ 2013-11-07: initial commit
benchmark/benchmark.css ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ background-color: #eae6d1;
3
+ font-family: sans-serif;
4
+ }
5
+ canvas {
6
+ display: block;
7
+ margin-left: auto;
8
+ margin-right: auto;
9
+ cursor: default;
10
+ }
11
+ canvas.pixelated {
12
+ image-rendering: -moz-crisp-edges;
13
+ image-rendering: -webkit-optimize-contrast;
14
+ image-rendering: pixelated;
15
+ -ms-interpolation-mode: nearest-neighbor;
16
+ }
17
+ div#sqSpinner {
18
+ position: fixed;
19
+ margin: auto; top: 0; left: 0; bottom: 0; right: 0;
20
+ width: 100px;
21
+ height: 100px;
22
+ border-radius: 50px;
23
+ background: rgba(0, 0, 0, 0.3);
24
+ box-shadow: 0 0 5px 5px #F90;
25
+ display: none;
26
+ transform: rotate(30deg);
27
+ }
28
+ div#sqSpinner > div {
29
+ position: absolute;
30
+ top: 45px;
31
+ left: 5px;
32
+ width: 90px;
33
+ height: 10px;
34
+ border-radius: 5px;
35
+ box-shadow: 0 0 5px 5px #F90;
36
+ }
benchmark/benchmark.html ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <!--
4
+ Copyright (c) 2013-2025 Vanessa Freudenberg
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ -->
24
+ <head>
25
+ <title>SqueakJS Benchmarks</title>
26
+ <link rel="stylesheet" href="benchmark.css">
27
+ <script type="text/javascript" src="http://www.google.com/jsapi"></script>
28
+ <script type="module" src="../squeak.js"></script>
29
+ <script src="benchmark.js"></script>
30
+
31
+ </head>
32
+ <body>
33
+ <h1>SqueakJS Benchmarks</h1>
34
+ <p id="results">
35
+ This is Tim Felgentreff's page for <a href="../">SqueakJS</a> benchmarking.
36
+ Please wait until the runs are finished.
37
+ </p>
38
+ <canvas id="sqCanvas" width="800" height="600"></canvas>
39
+ <div id="sqSpinner"><div></div></div>
40
+ </body>
41
+ </html>
benchmark/benchmark.image ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f9252e14a52e916675451f46e3505d1f659e902e3f2defc9aa2993c2b1565d86
3
+ size 650948
benchmark/benchmark.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Copyright (c) 2013-2025 Vanessa Freudenberg
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ * THE SOFTWARE.
21
+ */
22
+
23
+ window.stopVM = false;
24
+
25
+ window.onload = function() {
26
+
27
+ // Wrap the file close primitive to stop after the benchmark
28
+ // output file has been written
29
+ var origFileClose = Squeak.Primitives.prototype.fileClose
30
+ Squeak.Primitives.prototype.fileClose = (function (file) {
31
+ var contents = Squeak.bytesAsString(new Uint8Array(file.contents.buffer));
32
+ var r = document.getElementById("results");
33
+ r.innerHTML = "Your machine: " + navigator.userAgent + "<br>" +
34
+ "Your results:<br>" + contents.replace(/\r/g, "<br>") + "<br>"
35
+ saveToLively(contents);
36
+ SqueakJS.quitSqueak();
37
+ return origFileClose.apply(this, arguments);
38
+ });
39
+
40
+ if (!Date.now) {
41
+ Date.now = function now() {
42
+ return new Date().getTime();
43
+ };
44
+ }
45
+
46
+ function saveToLively(contents) {
47
+ var address = (window.google &&
48
+ google.loader &&
49
+ google.loader.ClientLocation &&
50
+ google.loader.ClientLocation.address) || {city: "unknown city", country: "unknown country"};
51
+ contents = navigator.userAgent + "\n" +
52
+ address.city + "\n" +
53
+ address.country + "\n" +
54
+ Date.now() + "\n" +
55
+ Squeak.vmVersion + "\n" +
56
+ contents;
57
+ var oReq = new XMLHttpRequest();
58
+ oReq.open(
59
+ "get",
60
+ "http://www.lively-kernel.org/babelsberg/nodejs/SqueakJSServer/?benchmarkResults=" +
61
+ encodeURIComponent(contents),
62
+ true);
63
+ oReq.send();
64
+ };
65
+
66
+ SqueakJS.runSqueak('benchmark.image', sqCanvas, {
67
+ spinner: sqSpinner,
68
+ onQuit: function() {
69
+ sqCanvas.style.display = "none";
70
+ },
71
+ });
72
+ };
demo/JSBridge.st ADDED
@@ -0,0 +1 @@
 
 
1
+ Exception subclass: #JSException
demo/SimplePlugin.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * SimplePlugin.js: Example for an external Squeak VM module
3
+ *
4
+ * primNavigatorInfo: anInteger
5
+ * <primitive: 'primitiveNavigatorInfo' module: 'SimplePlugin'>
6
+ * ^ self primitiveFailed
7
+ */
8
+
9
+ function SimplePlugin() {
10
+ var interpreterProxy,
11
+ primHandler;
12
+
13
+ function setInterpreter(anInterpreterProxy) {
14
+ // Slang interface
15
+ interpreterProxy = anInterpreterProxy;
16
+ // PrimHandler methods for convenience
17
+ primHandler = interpreterProxy.vm.primHandler;
18
+ // success
19
+ return true;
20
+ };
21
+
22
+ function primitiveNavigatorInfo(argCount) {
23
+ if (argCount !== 1) return false; // fail
24
+ var which = interpreterProxy.stackIntegerValue(0);
25
+ if (interpreterProxy.failed()) return false; // fail
26
+ var result = getNavigatorInfo(which);
27
+ if (!result) return false; // fail
28
+ var resultObj = primHandler.makeStString(result);
29
+ interpreterProxy.popthenPush(1 + argCount, resultObj);
30
+ return true; // success
31
+ };
32
+
33
+ function getNavigatorInfo(index) {
34
+ switch (index) {
35
+ case 1: return navigator.userAgent;
36
+ case 2: return navigator.language;
37
+ }
38
+ };
39
+
40
+ // hide private functions
41
+ return {
42
+ setInterpreter: setInterpreter,
43
+ primitiveNavigatorInfo: primitiveNavigatorInfo,
44
+ }
45
+ };
46
+
47
+ // register plugin in global Squeak object
48
+ window.addEventListener("load", function() {
49
+ Squeak.registerExternalModule('SimplePlugin', SimplePlugin());
50
+ });
51
+
52
+ /**********************************
53
+ NOTE: the mini.image does not have compiler support for
54
+ named primitives, yet. You need to declare it manually
55
+ using prim 117:
56
+
57
+ primNavigatorInfo: anInteger
58
+ <primitive: 117>
59
+ #(SimplePlugin primitiveNavigatorInfo 0 0) at: 1.
60
+ ^ self primitiveFailed
61
+ ***********************************/
demo/icon.png ADDED
demo/index.html ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>SqueakJS</title>
6
+ <!--
7
+ Copyright (c) 2013-2025 Vanessa Freudenberg
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in
17
+ all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ THE SOFTWARE.
26
+ -->
27
+ <link rel="icon" type="image/png" href="icon.png">
28
+ <link rel="apple-touch-icon" href="icon.png" sizes="152x152">
29
+ <meta name="viewport" content="minimum-scale=1,maximum-scale=1,width=device-width">
30
+ <meta name="apple-mobile-web-app-capable" content="yes">
31
+ <meta name="msapplication-TileImage" content="icon.png">
32
+
33
+ <link rel="stylesheet" href="../lib/addtohomescreen.css">
34
+ <script src="../lib/addtohomescreen.js"></script>
35
+
36
+ <link rel="stylesheet" href="simple.css">
37
+ <script type="module" src="../squeak.js"></script>
38
+ <script src="simple.js"></script>
39
+ <script src="SimplePlugin.js"></script>
40
+
41
+ </head>
42
+ <body>
43
+ <canvas id="sqCanvas"></canvas>
44
+ <div id="sqSpinner"><div></div></div>
45
+ </body>
46
+ </html>
demo/mini.image ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec22448856129313ceac51968edabf25fe6819f33812bd8f2943a60bc292c9ea
3
+ size 624444
demo/simple.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ background-color: #333;
3
+ font-family: sans-serif;
4
+ }
5
+ canvas {
6
+ position: fixed;
7
+ background: #000;
8
+ cursor: default;
9
+ user-select: none;
10
+ -webkit-user-select: none;
11
+ -moz-user-select: none;
12
+ }
13
+ canvas.pixelated {
14
+ image-rendering: -moz-crisp-edges;
15
+ image-rendering: -webkit-optimize-contrast;
16
+ image-rendering: pixelated;
17
+ -ms-interpolation-mode: nearest-neighbor;
18
+ }
19
+ div#sqSpinner {
20
+ position: fixed;
21
+ margin: auto; top: 0; left: 0; bottom: 0; right: 0;
22
+ width: 100px;
23
+ height: 100px;
24
+ border-radius: 50px;
25
+ background: rgba(0, 0, 0, 0.3);
26
+ box-shadow: 0 0 5px 5px #F90;
27
+ display: none;
28
+ transform: rotate(30deg);
29
+ }
30
+ div#sqSpinner > div {
31
+ position: absolute;
32
+ top: 45px;
33
+ left: 5px;
34
+ width: 90px;
35
+ height: 10px;
36
+ border-radius: 5px;
37
+ box-shadow: 0 0 5px 5px #F90;
38
+ }
demo/simple.html ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>SqueakJS</title>
6
+ <!--
7
+ Copyright (c) 2013-2025 Vanessa Freudenberg
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in
17
+ all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ THE SOFTWARE.
26
+ -->
27
+ <link rel="icon" type="image/png" href="icon.png">
28
+ <link rel="apple-touch-icon" href="icon.png" sizes="152x152">
29
+ <meta name="viewport" content="minimum-scale=1,maximum-scale=1,width=device-width">
30
+ <meta name="apple-mobile-web-app-capable" content="yes">
31
+ <meta name="msapplication-TileImage" content="icon.png">
32
+
33
+ <link rel="stylesheet" href="../lib/addtohomescreen.css">
34
+ <script src="../lib/addtohomescreen.js"></script>
35
+
36
+ <link rel="stylesheet" href="simple.css">
37
+ <script type="module" src="../squeak.js"></script>
38
+ <script src="simple.js"></script>
39
+ <script src="SimplePlugin.js"></script>
40
+
41
+ </head>
42
+ <body>
43
+ <canvas id="sqCanvas"></canvas>
44
+ <div id="sqSpinner"><div></div></div>
45
+ </body>
46
+ </html>
demo/simple.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Copyright (c) 2013-2025 Vanessa Freudenberg
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ * THE SOFTWARE.
21
+ */
22
+
23
+
24
+ window.onload = function() {
25
+ SqueakJS.runSqueak('squeakjs.image', sqCanvas, {
26
+ appName: "SqueakJS",
27
+ files: ['squeakjs.image', 'squeakjs.changes'],
28
+ spinner: sqSpinner,
29
+ });
30
+ };
31
+
32
+ if (!addToHomescreen.isStandalone) addToHomescreen({
33
+ appID: 'squeakjs.demo.add2home',
34
+ });
demo/squeakjs.changes ADDED
The diff for this file is too large to render. See raw diff
 
demo/squeakjs.image ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec22448856129313ceac51968edabf25fe6819f33812bd8f2943a60bc292c9ea
3
+ size 624444
dist/squeak_bundle.js ADDED
The diff for this file is too large to render. See raw diff
 
dist/squeak_headless_bundle.js ADDED
The diff for this file is too large to render. See raw diff
 
etoys/etoys.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ background-color: #333;
3
+ font-family: sans-serif;
4
+ }
5
+ canvas {
6
+ position: fixed;
7
+ background: #000;
8
+ cursor: default;
9
+ user-select: none;
10
+ -webkit-user-select: none;
11
+ -moz-user-select: none;
12
+ }
13
+ canvas.pixelated {
14
+ image-rendering: -moz-crisp-edges;
15
+ image-rendering: -webkit-optimize-contrast;
16
+ image-rendering: pixelated;
17
+ -ms-interpolation-mode: nearest-neighbor;
18
+ }
19
+ div#sqSpinner {
20
+ position: fixed;
21
+ margin: auto; top: 0; left: 0; bottom: 0; right: 0;
22
+ width: 100px;
23
+ height: 100px;
24
+ border-radius: 50px;
25
+ background: rgba(0, 0, 0, 0.3);
26
+ box-shadow: 0 0 5px 5px #F90;
27
+ display: none;
28
+ transform: rotate(30deg);
29
+ }
30
+ div#sqSpinner > div {
31
+ position: absolute;
32
+ top: 45px;
33
+ left: 5px;
34
+ width: 90px;
35
+ height: 10px;
36
+ border-radius: 5px;
37
+ box-shadow: 0 0 5px 5px #F90;
38
+ }
etoys/etoys.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Copyright (c) 2013-2025 Vanessa Freudenberg
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ * THE SOFTWARE.
21
+ */
22
+
23
+
24
+ window.onload = function() {
25
+ var url = "https://freudenbergs.de/vanessa/squeakjs/etoys.image";
26
+ SqueakJS.runSqueak(url, sqCanvas, {
27
+ appName: "Etoys",
28
+ fixedWidth: 1200,
29
+ fixedHeight: 900,
30
+ spinner: sqSpinner,
31
+ files: ["Etoys/EtoysV5.stc"],
32
+ root: "/Etoys",
33
+ templates: { "/Etoys": "Etoys" },
34
+ onStart: function(vm, display, options) {
35
+ // debugger
36
+ // vm.breakOn("Latin1Environment class>>systemConverterClass");
37
+ },
38
+ });
39
+ };
40
+
41
+ if (!addToHomescreen.isStandalone) addToHomescreen({
42
+ appID: 'squeakjs.etoys.add2home',
43
+ });
etoys/etoys.png ADDED
etoys/index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>EtoysJS</title>
6
+ <!--
7
+ Copyright (c) 2013-2025 Vanessa Freudenberg
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in
17
+ all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ THE SOFTWARE.
26
+ -->
27
+ <link rel="icon" type="image/png" href="etoys.png">
28
+ <link rel="apple-touch-icon" href="etoys.png" sizes="152x152">
29
+ <meta name="viewport" content="minimum-scale=1,maximum-scale=1,width=device-width">
30
+ <meta name="apple-mobile-web-app-capable" content="yes">
31
+ <meta name="msapplication-TileImage" content="etoys.png">
32
+
33
+ <link rel="stylesheet" href="../lib/addtohomescreen.css">
34
+ <script src="../lib/addtohomescreen.js"></script>
35
+
36
+ <link rel="stylesheet" href="etoys.css">
37
+ <script type="module" src="../squeak.js"></script>
38
+ <script src="etoys.js"></script>
39
+
40
+ </head>
41
+ <body>
42
+ <canvas id="sqCanvas" width="1200" height="900"></canvas>
43
+ <div id="sqSpinner"><div></div></div>
44
+ </body>
45
+ </html>
ffi/libc.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2013-2025 Vanessa Freudenberg
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in
13
+ * all copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ * THE SOFTWARE.
22
+ */
23
+
24
+ // This is a minimal libc module for SqueakJS
25
+ // serving mostly as a demo for FFI
26
+
27
+ function libc() {
28
+ return {
29
+ // LIBC module
30
+ getModuleName() { return "libc (SqueakJS)"; },
31
+ setInterpreter(proxy) { this.vm = proxy.vm; return true; },
32
+
33
+ // helper functions
34
+ bytesToString(bytes) {
35
+ const zero = bytes.indexOf(0);
36
+ if (zero >= 0) bytes = bytes.subarray(0, zero);
37
+ return String.fromCharCode.apply(null, bytes);
38
+ },
39
+ stringToBytes(string, bytes) {
40
+ for (let i = 0; i < string.length; i++) bytes[i] = string.charCodeAt(i);
41
+ bytes[string.length] = 0;
42
+ return bytes;
43
+ },
44
+
45
+ // LIBC emulation functions called via FFI
46
+ getenv(v) {
47
+ v = this.bytesToString(v);
48
+ switch (v) {
49
+ case "USER": return this.vm.options.user || "squeak";
50
+ case "HOME": return this.vm.options.root || "/";
51
+ }
52
+ this.vm.warnOnce("UNIMPLEMENTED getenv: " + v);
53
+ return null;
54
+ },
55
+ getcwd(buf, size) {
56
+ const cwd = this.vm.options.root || "/";
57
+ if (!buf) buf = new Uint8Array(cwd.length + 1);
58
+ if (size < cwd.length + 1) return 0; // should set errno to ERANGE
59
+ this.stringToBytes(cwd, buf);
60
+ return buf; // converted to Smalltalk String by FFI if declared as char*
61
+ },
62
+ };
63
+ }
64
+
65
+ function registerLibC() {
66
+ if (typeof Squeak === "object" && Squeak.registerExternalModule) {
67
+ Squeak.registerExternalModule('libc', libc());
68
+ } else self.setTimeout(registerLibC, 100);
69
+ };
70
+
71
+ registerLibC();
ffi/opengl.js ADDED
The diff for this file is too large to render. See raw diff
 
globals.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2013-2025 Vanessa Freudenberg
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in
13
+ * all copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ * THE SOFTWARE.
22
+ */
23
+
24
+ // Create Squeak VM namespace
25
+ if (!self.Squeak) self.Squeak = {};
26
+
27
+ // Setup a storage for settings
28
+ if (!Squeak.Settings) {
29
+ // Try (a working) localStorage and fall back to regular dictionary otherwise
30
+ var settings;
31
+ try {
32
+ // fails in restricted iframe
33
+ settings = self.localStorage;
34
+ settings["squeak-foo:"] = "bar";
35
+ if (settings["squeak-foo:"] !== "bar") throw Error();
36
+ delete settings["squeak-foo:"];
37
+ } catch(e) {
38
+ settings = {};
39
+ }
40
+ Squeak.Settings = settings;
41
+ }
42
+
43
+ if (!Object.extend) {
44
+ // Extend object by adding specified properties
45
+ Object.extend = function(obj /* + more args */ ) {
46
+ // skip arg 0, copy properties of other args to obj
47
+ for (var i = 1; i < arguments.length; i++)
48
+ if (typeof arguments[i] == 'object')
49
+ for (var name in arguments[i])
50
+ obj[name] = arguments[i][name];
51
+ };
52
+ }
53
+
54
+
55
+ // This mimics the Lively Kernel's subclassing scheme.
56
+ // When running there, Lively's subclasses and modules are used.
57
+ // Modules serve as namespaces in Lively. SqueakJS uses a flat namespace
58
+ // named "Squeak", but the code below still supports hierarchical names.
59
+ if (!Function.prototype.subclass) {
60
+ // Create subclass using specified class path and given properties
61
+ Function.prototype.subclass = function(classPath /* + more args */ ) {
62
+ // create subclass
63
+ var subclass = function() {
64
+ if (this.initialize) {
65
+ var result = this.initialize.apply(this, arguments);
66
+ if (result !== undefined) return result;
67
+ }
68
+ return this;
69
+ };
70
+ // set up prototype
71
+ var protoclass = function() { };
72
+ protoclass.prototype = this.prototype;
73
+ subclass.prototype = new protoclass();
74
+ // skip arg 0, copy properties of other args to prototype
75
+ for (var i = 1; i < arguments.length; i++)
76
+ Object.extend(subclass.prototype, arguments[i]);
77
+ // add class to namespace
78
+ var path = classPath.split("."),
79
+ className = path.pop(),
80
+ // Walk path starting at the global namespace (self)
81
+ // creating intermediate namespaces if necessary
82
+ namespace = path.reduce(function(namespace, path) {
83
+ if (!namespace[path]) namespace[path] = {};
84
+ return namespace[path];
85
+ }, self);
86
+ namespace[className] = subclass;
87
+ return subclass;
88
+ };
89
+
90
+ }
headless/headless.image ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:29621e0880d7cef148d17065934aca4ea993cb936ed51f98827d3bb97b18025c
3
+ size 135472
headless/index.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+ <!-- For browser that do support ES6 modules for WebWorkers -->
6
+ <!--
7
+ <script type="module">
8
+ new Worker("../squeak_headless.js?imageName=../headless/headless.image", { type: "module" });
9
+ </script>
10
+ -->
11
+ <!-- For browsers without support for ES6 modules for WebWorkers use bundled JS file -->
12
+ <script>
13
+ console.log("Starting SqueakJS in a WebWorker...");
14
+ new Worker("../dist/squeak_headless_bundle.js?imageName=../headless/headless.image");
15
+ </script>
16
+ </head>
17
+ <body>
18
+ Please open the developer console to see the console messages produced by the Smalltalk image.
19
+ </body>
20
+ </html>
jit.js ADDED
@@ -0,0 +1,1169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2014-2025 Vanessa Freudenberg
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in
13
+ * all copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ * THE SOFTWARE.
22
+ */
23
+
24
+ Object.subclass('Squeak.Compiler',
25
+
26
+ /****************************************************************************
27
+
28
+ VM and Compiler
29
+ ===============
30
+
31
+ The VM has an interpreter, it will work fine (and much more memory-efficient)
32
+ without loading a compiler. The compiler plugs into the VM by providing the
33
+ Squeak.Compiler global. It can be easily replaced by just loading a different
34
+ script providing Squeak.Compiler.
35
+
36
+ The VM creates the compiler instance after an image has been loaded and the VM
37
+ been initialized. Whenever a method is activated that was not compiled yet, the
38
+ compiler gets a chance to compile it. The compiler may decide to wait for a couple
39
+ of activations before actually compiling it. This might prevent do-its from ever
40
+ getting compiled, because they are only activated once. Therefore, the compiler
41
+ is also called when a long-running non-optimized loop calls checkForInterrupts.
42
+ Finally, whenever the interpreter is about to execute a bytecode, it calls the
43
+ compiled method instead (which typically will execute many bytecodes):
44
+
45
+ initialize:
46
+ compiler = new Squeak.Compiler(vm);
47
+
48
+ executeNewMethod, checkForInterrupts:
49
+ if (!method.compiled && compiler)
50
+ compiler.compile(method);
51
+
52
+ interpret:
53
+ if (method.compiled) method.compiled(vm);
54
+
55
+ Note that a compiler could hook itself into a compiled method by dispatching
56
+ to vm.compiler in the generated code. This would allow gathering statistics,
57
+ recompiling with optimized code etc.
58
+
59
+
60
+ About This Compiler
61
+ ===================
62
+
63
+ The compiler in this file is meant to be simple, fast-compiling, and general.
64
+ It transcribes bytecodes 1-to-1 into equivalent JavaScript code using
65
+ templates (and thus can even support single-stepping). It uses the
66
+ interpreter's stack pointer (SP) and program counter (PC), actual context
67
+ objects just like the interpreter, no register mapping, it does not optimize
68
+ sends, etc.
69
+
70
+ Jumps are handled by wrapping the whole method in a loop and switch. This also
71
+ enables continuing in the middle of a compiled method: whenever another context
72
+ is activated, the method returns to the main loop, and is entered again later
73
+ with a different PC. Here is an example method, its bytecodes, and a simplified
74
+ version of the generated JavaScript code:
75
+
76
+ method
77
+ [value selector] whileFalse.
78
+ ^ 42
79
+
80
+ 0 <00> pushInstVar: 0
81
+ 1 <D0> send: #selector
82
+ 2 <A8 02> jumpIfTrue: 6
83
+ 4 <A3 FA> jumpTo: 0
84
+ 6 <21> pushConst: 42
85
+ 7 <7C> return: topOfStack
86
+
87
+ context = vm.activeContext
88
+ while (true) switch (vm.pc) {
89
+ case 0:
90
+ stack[++vm.sp] = inst[0];
91
+ vm.pc = 2; vm.send(#selector); // activate new method
92
+ return; // return to main loop
93
+ // Main loop will execute the activated method. When
94
+ // that method returns, this method will be called
95
+ // again with vm.pc == 2 and jump directly to case 2
96
+ case 2:
97
+ if (stack[vm.sp--] === vm.trueObj) {
98
+ vm.pc = 6;
99
+ continue; // jump to case 6
100
+ }
101
+ // otherwise fall through to next case
102
+ case 4:
103
+ vm.pc = 0;
104
+ continue; // jump to case 0
105
+ case 6:
106
+ stack[++vm.sp] = 42;
107
+ vm.pc = 7; vm.doReturn(stack[vm.sp]);
108
+ return;
109
+ }
110
+
111
+ Debugging support
112
+ =================
113
+
114
+ This compiler supports generating single-stepping code and comments, which are
115
+ rather helpful during debugging.
116
+
117
+ Normally, only bytecodes that can be a jump target are given a label. Also,
118
+ bytecodes following a send operation need a label, to enable returning to that
119
+ spot after the context switch. All other bytecodes are executed continuously.
120
+
121
+ When compiling for single-stepping, each bytecode gets a label, and after each
122
+ bytecode a flag is checked and the method returns if needed. Because this is
123
+ a performance penalty, methods are first compiled without single-step support,
124
+ and recompiled for single-stepping on demand.
125
+
126
+ This is optional, another compiler can answer false from enableSingleStepping().
127
+ In that case the VM will delete the compiled method and invoke the interpreter
128
+ to single-step.
129
+
130
+ *****************************************************************************/
131
+
132
+ 'initialization', {
133
+ initialize: function(vm) {
134
+ this.vm = vm;
135
+ this.comments = !!Squeak.Compiler.comments, // generate comments
136
+ // for debug-printing only
137
+ this.specialSelectors = ['+', '-', '<', '>', '<=', '>=', '=', '~=', '*', '/', '\\\\', '@',
138
+ 'bitShift:', '//', 'bitAnd:', 'bitOr:', 'at:', 'at:put:', 'size', 'next', 'nextPut:',
139
+ 'atEnd', '==', 'class', 'blockCopy:', 'value', 'value:', 'do:', 'new', 'new:', 'x', 'y'];
140
+ this.doitCounter = 0;
141
+ this.blockCounter = 0;
142
+ },
143
+ },
144
+ 'accessing', {
145
+ compile: function(method, optClassObj, optSelObj) {
146
+ if (method.compiled === undefined) {
147
+ // 1st time
148
+ method.compiled = false;
149
+ } else {
150
+ // 2nd time
151
+ this.singleStep = false;
152
+ this.debug = this.comments;
153
+ var clsName, sel, instVars;
154
+ if (this.debug && !optClassObj) {
155
+ // this is expensive, so only do it when debugging
156
+ var isMethod = method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod];
157
+ this.vm.allMethodsDo(function(classObj, methodObj, selectorObj) {
158
+ if (isMethod ? methodObj === method : methodObj.pointers.includes(method)) {
159
+ optClassObj = classObj;
160
+ optSelObj = selectorObj;
161
+ return true;
162
+ }
163
+ });
164
+ }
165
+ if (optClassObj) {
166
+ clsName = optClassObj.className();
167
+ sel = optSelObj.bytesAsString();
168
+ if (this.debug) {
169
+ // only when debugging
170
+ var isMethod = method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod];
171
+ if (!isMethod) {
172
+ clsName = "[] in " + clsName;
173
+ }
174
+ instVars = optClassObj.allInstVarNames();
175
+ }
176
+ }
177
+ method.compiled = this.generate(method, clsName, sel, instVars);
178
+ }
179
+ },
180
+ enableSingleStepping: function(method, optClass, optSel) {
181
+ // recompile method for single-stepping
182
+ if (!method.compiled || !method.compiled.canSingleStep) {
183
+ this.singleStep = true; // generate breakpoint support
184
+ this.debug = true;
185
+ if (!optClass) {
186
+ this.vm.allMethodsDo(function(classObj, methodObj, selectorObj) {
187
+ if (methodObj === method) {
188
+ optClass = classObj;
189
+ optSel = selectorObj;
190
+ return true;
191
+ }
192
+ });
193
+ }
194
+ var cls = optClass && optClass.className();
195
+ var sel = optSel && optSel.bytesAsString();
196
+ var instVars = optClass && optClass.allInstVarNames();
197
+ method.compiled = this.generate(method, cls, sel, instVars);
198
+ method.compiled.canSingleStep = true;
199
+ }
200
+ // if a compiler does not support single-stepping, return false
201
+ return true;
202
+ },
203
+ functionNameFor: function(cls, sel) {
204
+ if (cls === undefined || cls === '?') {
205
+ var isMethod = this.method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod];
206
+ return isMethod ? "DOIT_" + ++this.doitCounter : "BLOCK_" + ++this.blockCounter;
207
+ }
208
+ cls = cls.replace(/ /g, "_").replace("[]", "Block");
209
+ if (!/[^a-zA-Z0-9:_]/.test(sel))
210
+ return cls + "_" + sel.replace(/:/g, "ː"); // unicode colon is valid in JS identifiers
211
+ var op = sel.replace(/./g, function(char) {
212
+ var repl = {'|': "OR", '~': "NOT", '<': "LT", '=': "EQ", '>': "GT",
213
+ '&': "AND", '@': "AT", '*': "TIMES", '+': "PLUS", '\\': "MOD",
214
+ '-': "MINUS", ',': "COMMA", '/': "DIV", '?': "IF"}[char];
215
+ return repl || 'OPERATOR';
216
+ });
217
+ return cls + "__" + op + "__";
218
+ },
219
+ },
220
+ 'generating', {
221
+ generate: function(method, optClass, optSel, optInstVarNames) {
222
+ this.method = method;
223
+ this.sista = method.methodSignFlag();
224
+ this.pc = 0; // next bytecode
225
+ this.endPC = 0; // pc of furthest jump target
226
+ this.prevPC = 0; // pc at start of current instruction
227
+ this.source = []; // snippets will be joined in the end
228
+ this.sourceLabels = {}; // source pos of generated jump labels
229
+ this.needsLabel = {}; // jump targets
230
+ this.sourcePos = {}; // source pos of optional vars / statements
231
+ this.needsVar = {}; // true if var was used
232
+ this.needsBreak = false; // insert break check for previous bytecode
233
+ if (optClass && optSel)
234
+ this.source.push("// ", optClass, ">>", optSel, "\n");
235
+ this.instVarNames = optInstVarNames;
236
+ this.allVars = ['context', 'stack', 'rcvr', 'inst[', 'temp[', 'lit['];
237
+ this.sourcePos['context'] = this.source.length; this.source.push("var context = vm.activeContext;\n");
238
+ this.sourcePos['stack'] = this.source.length; this.source.push("var stack = context.pointers;\n");
239
+ this.sourcePos['rcvr'] = this.source.length; this.source.push("var rcvr = vm.receiver;\n");
240
+ this.sourcePos['inst['] = this.source.length; this.source.push("var inst = rcvr.pointers;\n");
241
+ this.sourcePos['temp['] = this.source.length; this.source.push("var temp = vm.homeContext.pointers;\n");
242
+ this.sourcePos['lit['] = this.source.length; this.source.push("var lit = vm.method.pointers;\n");
243
+ this.sourcePos['loop-start'] = this.source.length; this.source.push("while (true) switch (vm.pc) {\ncase 0:\n");
244
+ if (this.sista) this.generateSista(method);
245
+ else this.generateV3(method);
246
+ var funcName = this.functionNameFor(optClass, optSel);
247
+ if (this.singleStep) {
248
+ if (this.debug) this.source.push("// all valid PCs have a label;\n");
249
+ this.source.push("default: throw Error('invalid PC');\n}"); // all PCs handled
250
+ } else {
251
+ this.sourcePos['loop-end'] = this.source.length; this.source.push("default: vm.interpretOne(true); return;\n}");
252
+ this.deleteUnneededLabels();
253
+ }
254
+ this.deleteUnneededVariables();
255
+ var source = "'use strict';\nreturn function " + funcName + "(vm) {\n" + this.source.join("") + "}";
256
+ return new Function(source)();
257
+ },
258
+ generateV3: function(method) {
259
+ this.done = false;
260
+ while (!this.done) {
261
+ var byte = method.bytes[this.pc++],
262
+ byte2 = 0;
263
+ switch (byte & 0xF8) {
264
+ // load inst var
265
+ case 0x00: case 0x08:
266
+ this.generatePush("inst[", byte & 0x0F, "]");
267
+ break;
268
+ // load temp var
269
+ case 0x10: case 0x18:
270
+ this.generatePush("temp[", 6 + (byte & 0xF), "]");
271
+ break;
272
+ // loadLiteral
273
+ case 0x20: case 0x28: case 0x30: case 0x38:
274
+ this.generatePush("lit[", 1 + (byte & 0x1F), "]");
275
+ break;
276
+ // loadLiteralIndirect
277
+ case 0x40: case 0x48: case 0x50: case 0x58:
278
+ this.generatePush("lit[", 1 + (byte & 0x1F), "].pointers[1]");
279
+ break;
280
+ // storeAndPop inst var
281
+ case 0x60:
282
+ this.generatePopInto("inst[", byte & 0x07, "]");
283
+ break;
284
+ // storeAndPop temp var
285
+ case 0x68:
286
+ this.generatePopInto("temp[", 6 + (byte & 0x07), "]");
287
+ break;
288
+ // Quick push
289
+ case 0x70:
290
+ switch (byte) {
291
+ case 0x70: this.generatePush("rcvr"); break;
292
+ case 0x71: this.generatePush("vm.trueObj"); break;
293
+ case 0x72: this.generatePush("vm.falseObj"); break;
294
+ case 0x73: this.generatePush("vm.nilObj"); break;
295
+ case 0x74: this.generatePush("-1"); break;
296
+ case 0x75: this.generatePush("0"); break;
297
+ case 0x76: this.generatePush("1"); break;
298
+ case 0x77: this.generatePush("2"); break;
299
+ }
300
+ break;
301
+ // Quick return
302
+ case 0x78:
303
+ switch (byte) {
304
+ case 0x78: this.generateReturn("rcvr"); break;
305
+ case 0x79: this.generateReturn("vm.trueObj"); break;
306
+ case 0x7A: this.generateReturn("vm.falseObj"); break;
307
+ case 0x7B: this.generateReturn("vm.nilObj"); break;
308
+ case 0x7C: this.generateReturn("stack[vm.sp]"); break;
309
+ case 0x7D: this.generateBlockReturn(); break;
310
+ default: throw Error("unusedBytecode " + byte);
311
+ }
312
+ break;
313
+ // Extended bytecodes
314
+ case 0x80: case 0x88:
315
+ this.generateV3Extended(byte);
316
+ break;
317
+ // short jump
318
+ case 0x90:
319
+ this.generateJump((byte & 0x07) + 1);
320
+ break;
321
+ // short conditional jump
322
+ case 0x98:
323
+ this.generateJumpIf(false, (byte & 0x07) + 1);
324
+ break;
325
+ // long jump, forward and back
326
+ case 0xA0:
327
+ byte2 = method.bytes[this.pc++];
328
+ this.generateJump(((byte&7)-4) * 256 + byte2);
329
+ break;
330
+ // long conditional jump
331
+ case 0xA8:
332
+ byte2 = method.bytes[this.pc++];
333
+ this.generateJumpIf(byte < 0xAC, (byte & 3) * 256 + byte2);
334
+ break;
335
+ // SmallInteger ops: + - < > <= >= = ~= * / @ lshift: lxor: land: lor:
336
+ case 0xB0: case 0xB8:
337
+ this.generateNumericOp(byte);
338
+ break;
339
+ // quick primitives: // at:, at:put:, size, next, nextPut:, ...
340
+ case 0xC0: case 0xC8:
341
+ this.generateQuickPrim(byte);
342
+ break;
343
+ // send literal selector
344
+ case 0xD0: case 0xD8:
345
+ this.generateSend("lit[", 1 + (byte & 0x0F), "]", 0, false);
346
+ break;
347
+ case 0xE0: case 0xE8:
348
+ this.generateSend("lit[", 1 + (byte & 0x0F), "]", 1, false);
349
+ break;
350
+ case 0xF0: case 0xF8:
351
+ this.generateSend("lit[", 1 + (byte & 0x0F), "]", 2, false);
352
+ break;
353
+ }
354
+ }
355
+ },
356
+ generateV3Extended: function(bytecode) {
357
+ var byte2, byte3;
358
+ switch (bytecode) {
359
+ // extended push
360
+ case 0x80:
361
+ byte2 = this.method.bytes[this.pc++];
362
+ switch (byte2 >> 6) {
363
+ case 0: this.generatePush("inst[", byte2 & 0x3F, "]"); return;
364
+ case 1: this.generatePush("temp[", 6 + (byte2 & 0x3F), "]"); return;
365
+ case 2: this.generatePush("lit[", 1 + (byte2 & 0x3F), "]"); return;
366
+ case 3: this.generatePush("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return;
367
+ }
368
+ // extended store
369
+ case 0x81:
370
+ byte2 = this.method.bytes[this.pc++];
371
+ switch (byte2 >> 6) {
372
+ case 0: this.generateStoreInto("inst[", byte2 & 0x3F, "]"); return;
373
+ case 1: this.generateStoreInto("temp[", 6 + (byte2 & 0x3F), "]"); return;
374
+ case 2: throw Error("illegal store into literal");
375
+ case 3: this.generateStoreInto("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return;
376
+ }
377
+ return;
378
+ // extended pop into
379
+ case 0x82:
380
+ byte2 = this.method.bytes[this.pc++];
381
+ switch (byte2 >> 6) {
382
+ case 0: this.generatePopInto("inst[", byte2 & 0x3F, "]"); return;
383
+ case 1: this.generatePopInto("temp[", 6 + (byte2 & 0x3F), "]"); return;
384
+ case 2: throw Error("illegal pop into literal");
385
+ case 3: this.generatePopInto("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return;
386
+ }
387
+ // Single extended send
388
+ case 0x83:
389
+ byte2 = this.method.bytes[this.pc++];
390
+ this.generateSend("lit[", 1 + (byte2 & 0x1F), "]", byte2 >> 5, false);
391
+ return;
392
+ // Double extended do-anything
393
+ case 0x84:
394
+ byte2 = this.method.bytes[this.pc++];
395
+ byte3 = this.method.bytes[this.pc++];
396
+ switch (byte2 >> 5) {
397
+ case 0: this.generateSend("lit[", 1 + byte3, "]", byte2 & 31, false); return;
398
+ case 1: this.generateSend("lit[", 1 + byte3, "]", byte2 & 31, true); return;
399
+ case 2: this.generatePush("inst[", byte3, "]"); return;
400
+ case 3: this.generatePush("lit[", 1 + byte3, "]"); return;
401
+ case 4: this.generatePush("lit[", 1 + byte3, "].pointers[1]"); return;
402
+ case 5: this.generateStoreInto("inst[", byte3, "]"); return;
403
+ case 6: this.generatePopInto("inst[", byte3, "]"); return;
404
+ case 7: this.generateStoreInto("lit[", 1 + byte3, "].pointers[1]"); return;
405
+ }
406
+ // Single extended send to super
407
+ case 0x85:
408
+ byte2 = this.method.bytes[this.pc++];
409
+ this.generateSend("lit[", 1 + (byte2 & 0x1F), "]", byte2 >> 5, true);
410
+ return;
411
+ // Second extended send
412
+ case 0x86:
413
+ byte2 = this.method.bytes[this.pc++];
414
+ this.generateSend("lit[", 1 + (byte2 & 0x3F), "]", byte2 >> 6, false);
415
+ return;
416
+ // pop
417
+ case 0x87:
418
+ this.generateInstruction("pop", "vm.sp--");
419
+ return;
420
+ // dup
421
+ case 0x88:
422
+ this.needsVar['stack'] = true;
423
+ this.generateInstruction("dup", "var dup = stack[vm.sp]; stack[++vm.sp] = dup");
424
+ return;
425
+ // thisContext
426
+ case 0x89:
427
+ this.needsVar['stack'] = true;
428
+ this.generateInstruction("push thisContext", "stack[++vm.sp] = vm.exportThisContext()");
429
+ return;
430
+ // closures
431
+ case 0x8A:
432
+ byte2 = this.method.bytes[this.pc++];
433
+ var popValues = byte2 > 127,
434
+ count = byte2 & 127;
435
+ this.generateClosureTemps(count, popValues);
436
+ return;
437
+ // call primitive
438
+ case 0x8B:
439
+ byte2 = this.method.bytes[this.pc++];
440
+ byte3 = this.method.bytes[this.pc++];
441
+ this.generateCallPrimitive(byte2 + 256 * byte3, 0x81);
442
+ return
443
+ // remote push from temp vector
444
+ case 0x8C:
445
+ byte2 = this.method.bytes[this.pc++];
446
+ byte3 = this.method.bytes[this.pc++];
447
+ this.generatePush("temp[", 6 + byte3, "].pointers[", byte2, "]");
448
+ return;
449
+ // remote store into temp vector
450
+ case 0x8D:
451
+ byte2 = this.method.bytes[this.pc++];
452
+ byte3 = this.method.bytes[this.pc++];
453
+ this.generateStoreInto("temp[", 6 + byte3, "].pointers[", byte2, "]");
454
+ return;
455
+ // remote store and pop into temp vector
456
+ case 0x8E:
457
+ byte2 = this.method.bytes[this.pc++];
458
+ byte3 = this.method.bytes[this.pc++];
459
+ this.generatePopInto("temp[", 6 + byte3, "].pointers[", byte2, "]");
460
+ return;
461
+ // pushClosureCopy
462
+ case 0x8F:
463
+ byte2 = this.method.bytes[this.pc++];
464
+ byte3 = this.method.bytes[this.pc++];
465
+ var byte4 = this.method.bytes[this.pc++];
466
+ var numArgs = byte2 & 0xF,
467
+ numCopied = byte2 >> 4,
468
+ blockSize = byte3 << 8 | byte4;
469
+ this.generateClosureCopy(numArgs, numCopied, blockSize);
470
+ return;
471
+ }
472
+ },
473
+ generateSista: function() {
474
+ var bytes = this.method.bytes,
475
+ b,
476
+ b2,
477
+ b3,
478
+ extA = 0,
479
+ extB = 0;
480
+ this.done = false;
481
+ while (!this.done) {
482
+ b = bytes[this.pc++];
483
+ switch (b) {
484
+ // 1 Byte Bytecodes
485
+
486
+ // load receiver variable
487
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
488
+ case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F:
489
+ this.generatePush("inst[", b & 0x0F, "]");
490
+ break;
491
+ // load literal variable
492
+ case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
493
+ case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F:
494
+ this.generatePush("lit[", 1 + (b & 0x0F), "].pointers[1]");
495
+ break;
496
+ // load literal constant
497
+ case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
498
+ case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
499
+ case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
500
+ case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
501
+ this.generatePush("lit[", 1 + (b & 0x1F), "]");
502
+ break;
503
+ // load temporary variable
504
+ case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
505
+ this.generatePush("temp[", 6 + (b & 0x07), "]");
506
+ break;
507
+ case 0x48: case 0x49: case 0x4A: case 0x4B:
508
+ this.generatePush("temp[", 6 + (b & 0x03) + 8, "]");
509
+ break;
510
+ case 0x4C: this.generatePush("rcvr");
511
+ break;
512
+ case 0x4D: this.generatePush("vm.trueObj");
513
+ break;
514
+ case 0x4E: this.generatePush("vm.falseObj");
515
+ break;
516
+ case 0x4F: this.generatePush("vm.nilObj");
517
+ break;
518
+ case 0x50: this.generatePush(0);
519
+ break;
520
+ case 0x51: this.generatePush(1);
521
+ break;
522
+ case 0x52:
523
+ this.needsVar['stack'] = true;
524
+ this.generateInstruction("push thisContext", "stack[++vm.sp] = vm.exportThisContext()");
525
+ break;
526
+ case 0x53:
527
+ this.needsVar['stack'] = true;
528
+ this.generateInstruction("dup", "var dup = stack[vm.sp]; stack[++vm.sp] = dup");
529
+ break;
530
+ case 0x54: case 0x55: case 0x56: case 0x57:
531
+ throw Error("unusedBytecode " + b);
532
+ case 0x58: this.generateReturn("rcvr");
533
+ break;
534
+ case 0x59: this.generateReturn("vm.trueObj");
535
+ break;
536
+ case 0x5A: this.generateReturn("vm.falseObj");
537
+ break;
538
+ case 0x5B: this.generateReturn("vm.nilObj");
539
+ break;
540
+ case 0x5C: this.generateReturn("stack[vm.sp]");
541
+ break;
542
+ case 0x5D: this.generateBlockReturn("vm.nilObj");
543
+ break;
544
+ case 0x5E: this.generateBlockReturn();
545
+ break;
546
+ case 0x5F: break; // nop
547
+ case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
548
+ case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
549
+ this.generateNumericOp(b);
550
+ break;
551
+ case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
552
+ case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
553
+ this.generateQuickPrim(b);
554
+ break;
555
+ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
556
+ case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F:
557
+ this.generateSend("lit[", 1 + (b & 0x0F), "]", 0, false);
558
+ break;
559
+ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
560
+ case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F:
561
+ this.generateSend("lit[", 1 + (b & 0x0F), "]", 1, false);
562
+ break;
563
+ case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7:
564
+ case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF:
565
+ this.generateSend("lit[", 1 + (b & 0x0F), "]", 2, false);
566
+ break;
567
+ case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7:
568
+ this.generateJump((b & 0x07) + 1);
569
+ break;
570
+ case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF:
571
+ this.generateJumpIf(true, (b & 0x07) + 1);
572
+ break;
573
+ case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7:
574
+ this.generateJumpIf(false, (b & 0x07) + 1);
575
+ break;
576
+ case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF:
577
+ this.generatePopInto("inst[", b & 0x07, "]");
578
+ break;
579
+ case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7:
580
+ this.generatePopInto("temp[", 6 + (b & 0x07), "]");
581
+ break;
582
+ case 0xD8: this.generateInstruction("pop", "vm.sp--");
583
+ break;
584
+ case 0xD9:
585
+ throw Error("unumplementedBytecode: 0xD9 (unconditional trap)");
586
+ case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF:
587
+ throw Error("unusedBytecode " + b);
588
+
589
+ // 2 Byte Bytecodes
590
+ case 0xE0:
591
+ b2 = bytes[this.pc++];
592
+ extA = extA * 256 + b2;
593
+ continue;
594
+ case 0xE1:
595
+ b2 = bytes[this.pc++];
596
+ extB = extB * 256 + (b2 < 128 ? b2 : b2 - 256);
597
+ continue;
598
+ case 0xE2:
599
+ b2 = bytes[this.pc++];
600
+ this.generatePush("inst[", b2 + extA * 256, "]");
601
+ break;
602
+ case 0xE3:
603
+ b2 = bytes[this.pc++];
604
+ this.generatePush("lit[", 1 + b2 + extA * 256, "].pointers[1]");
605
+ break;
606
+ case 0xE4:
607
+ b2 = bytes[this.pc++];
608
+ this.generatePush("lit[", 1 + b2 + extA * 256, "]");
609
+ break;
610
+ case 0xE5:
611
+ b2 = bytes[this.pc++];
612
+ this.generatePush("temp[", 6 + b2, "]");
613
+ break;
614
+ case 0xE6:
615
+ throw Error("unusedBytecode 0xE6");
616
+ case 0xE7:
617
+ b2 = bytes[this.pc++];
618
+ var popValues = b2 > 127,
619
+ count = b2 & 127;
620
+ this.generateClosureTemps(count, popValues);
621
+ break;
622
+ case 0xE8:
623
+ b2 = bytes[this.pc++];
624
+ this.generatePush(b2 + extB * 256);
625
+ break;
626
+ case 0xE9:
627
+ b2 = bytes[this.pc++];
628
+ this.generatePush("vm.image.getCharacter(", b2 + extB * 256, ")");
629
+ break;
630
+ case 0xEA:
631
+ b2 = bytes[this.pc++];
632
+ this.generateSend("lit[", 1 + (b2 >> 3) + (extA << 5), "]", (b2 & 7) + (extB << 3), false);
633
+ break;
634
+ case 0xEB:
635
+ b2 = bytes[this.pc++];
636
+ var lit = (b2 >> 3) + (extA << 5),
637
+ numArgs = (b2 & 7) + ((extB & 63) << 3),
638
+ directed = extB >= 64;
639
+ this.generateSend("lit[", 1 + lit, "]", numArgs, directed ? "directed" : true);
640
+ break;
641
+ case 0xEC:
642
+ throw Error("unimplemented bytecode: 0xEC (class trap)");
643
+ case 0xED:
644
+ b2 = bytes[this.pc++];
645
+ this.generateJump(b2 + extB * 256);
646
+ break;
647
+ case 0xEE:
648
+ b2 = bytes[this.pc++];
649
+ this.generateJumpIf(true, b2 + extB * 256);
650
+ break;
651
+ case 0xEF:
652
+ b2 = bytes[this.pc++];
653
+ this.generateJumpIf(false, b2 + extB * 256);
654
+ break;
655
+ case 0xF0:
656
+ b2 = bytes[this.pc++];
657
+ this.generatePopInto("inst[", b2 + extA * 256, "]");
658
+ break;
659
+ case 0xF1:
660
+ b2 = bytes[this.pc++];
661
+ this.generatePopInto("lit[", 1 + b2 + extA * 256, "].pointers[1]");
662
+ break;
663
+ case 0xF2:
664
+ b2 = bytes[this.pc++];
665
+ this.generatePopInto("temp[", 6 + b2, "]");
666
+ break;
667
+ case 0xF3:
668
+ b2 = bytes[this.pc++];
669
+ this.generateStoreInto("inst[", b2 + extA * 256, "]");
670
+ break;
671
+ case 0xF4:
672
+ b2 = bytes[this.pc++];
673
+ this.generateStoreInto("lit[", 1 + b2 + extA * 256, "].pointers[1]");
674
+ break;
675
+ case 0xF5:
676
+ b2 = bytes[this.pc++];
677
+ this.generateStoreInto("temp[", 6 + b2, "]");
678
+ break;
679
+ case 0xF6: case 0xF7:
680
+ throw Error("unusedBytecode " + b);
681
+
682
+ // 3 Byte Bytecodes
683
+
684
+ case 0xF8:
685
+ b2 = bytes[this.pc++];
686
+ b3 = bytes[this.pc++];
687
+ this.generateCallPrimitive(b2 + b3 * 256, 0xF5);
688
+ break;
689
+ case 0xF9:
690
+ b2 = bytes[this.pc++];
691
+ b3 = bytes[this.pc++];
692
+ this.generatePushFullClosure(b2 + extA * 255, b3);
693
+ break;
694
+ case 0xFA:
695
+ b2 = bytes[this.pc++];
696
+ b3 = bytes[this.pc++];
697
+ var numArgs = (b2 & 0x07) + (extA & 0x0F) * 8,
698
+ numCopied = (b2 >> 3 & 0x7) + (extA >> 4) * 8,
699
+ blockSize = b3 + (extB << 8);
700
+ this.generateClosureCopy(numArgs, numCopied, blockSize);
701
+ break;
702
+ case 0xFB:
703
+ b2 = bytes[this.pc++];
704
+ b3 = bytes[this.pc++];
705
+ this.generatePush("temp[", 6 + b3, "].pointers[", b2, "]");
706
+ break;
707
+ case 0xFC:
708
+ b2 = bytes[this.pc++];
709
+ b3 = bytes[this.pc++];
710
+ this.generateStoreInto("temp[", 6 + b3, "].pointers[", b2, "]");
711
+ break;
712
+ case 0xFD:
713
+ b2 = bytes[this.pc++];
714
+ b3 = bytes[this.pc++];
715
+ this.generatePopInto("temp[", 6 + b3, "].pointers[", b2, "]");
716
+ break;
717
+ case 0xFE: case 0xFF:
718
+ throw Error("unusedBytecode " + b);
719
+ default:
720
+ throw Error("illegal bytecode: " + b);
721
+ }
722
+ extA = 0;
723
+ extB = 0;
724
+ }
725
+ },
726
+ generatePush: function(target, arg1, suffix1, arg2, suffix2) {
727
+ if (this.debug) this.generateDebugCode("push", target, arg1, suffix1, arg2, suffix2);
728
+ this.generateLabel();
729
+ this.needsVar[target] = true;
730
+ this.needsVar['stack'] = true;
731
+ this.source.push("stack[++vm.sp] = ", target);
732
+ if (arg1 !== undefined) {
733
+ this.source.push(arg1, suffix1);
734
+ if (arg2 !== undefined) {
735
+ this.source.push(arg2, suffix2);
736
+ }
737
+ }
738
+ this.source.push(";\n");
739
+ },
740
+ generateStoreInto: function(target, arg1, suffix1, arg2, suffix2) {
741
+ if (this.debug) this.generateDebugCode("store into", target, arg1, suffix1, arg2, suffix2);
742
+ this.generateLabel();
743
+ this.needsVar[target] = true;
744
+ this.needsVar['stack'] = true;
745
+ this.source.push(target);
746
+ if (arg1 !== undefined) {
747
+ this.source.push(arg1, suffix1);
748
+ if (arg2 !== undefined) {
749
+ this.source.push(arg2, suffix2);
750
+ }
751
+ }
752
+ this.source.push(" = stack[vm.sp];\n");
753
+ this.generateDirty(target, arg1, suffix1);
754
+ },
755
+ generatePopInto: function(target, arg1, suffix1, arg2, suffix2) {
756
+ if (this.debug) this.generateDebugCode("pop into", target, arg1, suffix1, arg2, suffix2);
757
+ this.generateLabel();
758
+ this.needsVar[target] = true;
759
+ this.needsVar['stack'] = true;
760
+ this.source.push(target);
761
+ if (arg1 !== undefined) {
762
+ this.source.push(arg1, suffix1);
763
+ if (arg2 !== undefined) {
764
+ this.source.push(arg2, suffix2);
765
+ }
766
+ }
767
+ this.source.push(" = stack[vm.sp--];\n");
768
+ this.generateDirty(target, arg1, suffix1);
769
+ },
770
+ generateReturn: function(what) {
771
+ if (this.debug) this.generateDebugCode("return", what);
772
+ this.generateLabel();
773
+ this.needsVar[what] = true;
774
+ this.source.push(
775
+ "vm.pc = ", this.pc, "; vm.doReturn(", what, "); return;\n");
776
+ this.needsBreak = false; // returning anyway
777
+ this.done = this.pc > this.endPC;
778
+ },
779
+ generateBlockReturn: function(retVal) {
780
+ if (this.debug) this.generateDebugCode("block return");
781
+ this.generateLabel();
782
+ if (!retVal) {
783
+ this.needsVar['stack'] = true;
784
+ retVal = "stack[vm.sp--]";
785
+ }
786
+ // actually stack === context.pointers but that would look weird
787
+ this.needsVar['context'] = true;
788
+ this.source.push(
789
+ "vm.pc = ", this.pc, "; vm.doReturn(", retVal, ", context.pointers[0]); return;\n");
790
+ this.needsBreak = false; // returning anyway
791
+ this.done = this.pc > this.endPC;
792
+ },
793
+ generateJump: function(distance) {
794
+ var destination = this.pc + distance;
795
+ if (this.debug) this.generateDebugCode("jump to " + destination);
796
+ this.generateLabel();
797
+ this.needsVar['context'] = true;
798
+ this.source.push("vm.pc = ", destination, "; ");
799
+ if (distance < 0) this.source.push(
800
+ "\nif (vm.interruptCheckCounter-- <= 0) {\n",
801
+ " vm.checkForInterrupts();\n",
802
+ " if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n",
803
+ "}\n");
804
+ if (this.singleStep) this.source.push("\nif (vm.breakOutOfInterpreter) return;\n");
805
+ this.source.push("continue;\n");
806
+ this.needsBreak = false; // already checked
807
+ this.needsLabel[destination] = true;
808
+ if (destination > this.endPC) this.endPC = destination;
809
+ },
810
+ generateJumpIf: function(condition, distance) {
811
+ var destination = this.pc + distance;
812
+ if (this.debug) this.generateDebugCode("jump if " + condition + " to " + destination);
813
+ this.generateLabel();
814
+ this.needsVar['stack'] = true;
815
+ this.source.push(
816
+ "var cond = stack[vm.sp--]; if (cond === vm.", condition, "Obj) {vm.pc = ", destination, "; ");
817
+ if (this.singleStep) this.source.push("if (vm.breakOutOfInterpreter) return; else ");
818
+ this.source.push("continue}\n",
819
+ "else if (cond !== vm.", !condition, "Obj) {vm.sp++; vm.pc = ", this.pc, "; vm.send(vm.specialObjects[25], 0, false); return}\n");
820
+ this.needsLabel[this.pc] = true; // for coming back after #mustBeBoolean send
821
+ this.needsLabel[destination] = true; // obviously
822
+ if (destination > this.endPC) this.endPC = destination;
823
+ },
824
+ generateQuickPrim: function(byte) {
825
+ if (this.debug) this.generateDebugCode("quick send #" + this.specialSelectors[(byte & 0x0F) + 16]);
826
+ this.generateLabel();
827
+ switch (byte & 0x0F) {
828
+ case 0x0: // at:
829
+ this.needsVar['stack'] = true;
830
+ this.source.push(
831
+ "var a, b; if ((a=stack[vm.sp-1]).sqClass === vm.specialObjects[7] && a.pointers && typeof (b=stack[vm.sp]) === 'number' && b>0 && b<=a.pointers.length) {\n",
832
+ " stack[--vm.sp] = a.pointers[b-1];",
833
+ "} else { var c = vm.primHandler.objectAt(true,true,false); if (vm.primHandler.success) stack[--vm.sp] = c; else {\n",
834
+ " vm.pc = ", this.pc, "; vm.sendSpecial(16); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }}\n");
835
+ this.needsLabel[this.pc] = true;
836
+ return;
837
+ case 0x1: // at:put:
838
+ this.needsVar['stack'] = true;
839
+ this.source.push(
840
+ "var a, b; if ((a=stack[vm.sp-2]).sqClass === vm.specialObjects[7] && a.pointers && typeof (b=stack[vm.sp-1]) === 'number' && b>0 && b<=a.pointers.length) {\n",
841
+ " var c = stack[vm.sp]; stack[vm.sp-=2] = a.pointers[b-1] = c; a.dirty = true;",
842
+ "} else { vm.primHandler.objectAtPut(true,true,false); if (vm.primHandler.success) stack[vm.sp-=2] = c; else {\n",
843
+ " vm.pc = ", this.pc, "; vm.sendSpecial(17); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }}\n");
844
+ this.needsLabel[this.pc] = true;
845
+ return;
846
+ case 0x2: // size
847
+ this.needsVar['stack'] = true;
848
+ this.source.push(
849
+ "if (stack[vm.sp].sqClass === vm.specialObjects[7]) stack[vm.sp] = stack[vm.sp].pointersSize();\n", // Array
850
+ "else if (stack[vm.sp].sqClass === vm.specialObjects[6]) stack[vm.sp] = stack[vm.sp].bytesSize();\n", // ByteString
851
+ "else { vm.pc = ", this.pc, "; vm.sendSpecial(18); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }\n");
852
+ this.needsLabel[this.pc] = true;
853
+ return;
854
+ //case 0x3: return false; // next
855
+ //case 0x4: return false; // nextPut:
856
+ //case 0x5: return false; // atEnd
857
+ case 0x6: // ==
858
+ this.needsVar['stack'] = true;
859
+ this.source.push("var cond = stack[vm.sp-1] === stack[vm.sp];\nstack[--vm.sp] = cond ? vm.trueObj : vm.falseObj;\n");
860
+ return;
861
+ case 0x7: // class
862
+ this.needsVar['stack'] = true;
863
+ this.source.push("stack[vm.sp] = typeof stack[vm.sp] === 'number' ? vm.specialObjects[5] : stack[vm.sp].sqClass;\n");
864
+ return;
865
+ case 0x8: // blockCopy:
866
+ this.needsVar['rcvr'] = true;
867
+ this.source.push(
868
+ "vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), ")) ",
869
+ "{vm.sendSpecial(", ((byte & 0x0F) + 16), "); return}\n");
870
+ this.needsLabel[this.pc] = true; // for send
871
+ this.needsLabel[this.pc + 2] = true; // for start of block
872
+ return;
873
+ case 0x9: // value
874
+ case 0xA: // value:
875
+ case 0xB: // do:
876
+ this.needsVar['rcvr'] = true;
877
+ this.source.push(
878
+ "vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), ")) vm.sendSpecial(", ((byte & 0x0F) + 16), "); return;\n");
879
+ this.needsLabel[this.pc] = true;
880
+ return;
881
+ //case 0xC: return false; // new
882
+ //case 0xD: return false; // new:
883
+ //case 0xE: return false; // x
884
+ //case 0xF: return false; // y
885
+ }
886
+ // generic version for the bytecodes not yet handled above
887
+ this.needsVar['rcvr'] = true;
888
+ this.needsVar['context'] = true;
889
+ this.source.push(
890
+ "vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), "))",
891
+ " vm.sendSpecial(", ((byte & 0x0F) + 16), ");\n",
892
+ "if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n");
893
+ this.needsBreak = false; // already checked
894
+ // if falling back to a full send we need a label for coming back
895
+ this.needsLabel[this.pc] = true;
896
+ },
897
+ generateNumericOp: function(byte) {
898
+ if (this.debug) this.generateDebugCode("quick send #" + this.specialSelectors[byte & 0x0F]);
899
+ this.generateLabel();
900
+ // if the op cannot be executed here, do a full send and return to main loop
901
+ // we need a label for coming back
902
+ this.needsLabel[this.pc] = true;
903
+ switch (byte & 0x0F) {
904
+ case 0x0: // PLUS +
905
+ this.needsVar['stack'] = true;
906
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
907
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
908
+ " stack[--vm.sp] = vm.primHandler.signed32BitIntegerFor(a + b);\n",
909
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(0); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
910
+ return;
911
+ case 0x1: // MINUS -
912
+ this.needsVar['stack'] = true;
913
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
914
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
915
+ " stack[--vm.sp] = vm.primHandler.signed32BitIntegerFor(a - b);\n",
916
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(1); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
917
+ return;
918
+ case 0x2: // LESS <
919
+ this.needsVar['stack'] = true;
920
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
921
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
922
+ " stack[--vm.sp] = a < b ? vm.trueObj : vm.falseObj;\n",
923
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(2); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
924
+ return;
925
+ case 0x3: // GRTR >
926
+ this.needsVar['stack'] = true;
927
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
928
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
929
+ " stack[--vm.sp] = a > b ? vm.trueObj : vm.falseObj;\n",
930
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(3); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
931
+ return;
932
+ case 0x4: // LEQ <=
933
+ this.needsVar['stack'] = true;
934
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
935
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
936
+ " stack[--vm.sp] = a <= b ? vm.trueObj : vm.falseObj;\n",
937
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(4); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
938
+ return;
939
+ case 0x5: // GEQ >=
940
+ this.needsVar['stack'] = true;
941
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
942
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
943
+ " stack[--vm.sp] = a >= b ? vm.trueObj : vm.falseObj;\n",
944
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(5); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
945
+ return;
946
+ case 0x6: // EQU =
947
+ this.needsVar['stack'] = true;
948
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
949
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
950
+ " stack[--vm.sp] = a === b ? vm.trueObj : vm.falseObj;\n",
951
+ "} else if (a === b && a.float === a.float) {\n", // NaN check
952
+ " stack[--vm.sp] = vm.trueObj;\n",
953
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(6); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
954
+ return;
955
+ case 0x7: // NEQ ~=
956
+ this.needsVar['stack'] = true;
957
+ this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n",
958
+ "if (typeof a === 'number' && typeof b === 'number') {\n",
959
+ " stack[--vm.sp] = a !== b ? vm.trueObj : vm.falseObj;\n",
960
+ "} else if (a === b && a.float === a.float) {\n", // NaN check
961
+ " stack[--vm.sp] = vm.falseObj;\n",
962
+ "} else { vm.pc = ", this.pc, "; vm.sendSpecial(7); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n");
963
+ return;
964
+ case 0x8: // TIMES *
965
+ this.source.push("vm.success = true; vm.resultIsFloat = false; if(!vm.pop2AndPushNumResult(vm.stackIntOrFloat(1) * vm.stackIntOrFloat(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(8); return}\n");
966
+ return;
967
+ case 0x9: // DIV /
968
+ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.quickDivide(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(9); return}\n");
969
+ return;
970
+ case 0xA: // MOD \
971
+ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.mod(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(10); return}\n");
972
+ return;
973
+ case 0xB: // MakePt int@int
974
+ this.source.push("vm.success = true; if(!vm.primHandler.primitiveMakePoint(1, true)) { vm.pc = ", this.pc, "; vm.sendSpecial(11); return}\n");
975
+ return;
976
+ case 0xC: // bitShift:
977
+ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.safeShift(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(12); return}\n");
978
+ return;
979
+ case 0xD: // Divide //
980
+ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.div(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(13); return}\n");
981
+ return;
982
+ case 0xE: // bitAnd:
983
+ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.stackInteger(1) & vm.stackInteger(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(14); return}\n");
984
+ return;
985
+ case 0xF: // bitOr:
986
+ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.stackInteger(1) | vm.stackInteger(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(15); return}\n");
987
+ return;
988
+ }
989
+ },
990
+ generateSend: function(prefix, num, suffix, numArgs, superSend) {
991
+ if (this.debug) this.generateDebugCode(
992
+ (superSend === "directed" ? "directed super send " : superSend ? "super send " : "send ")
993
+ + (prefix === "lit[" ? this.method.pointers[num].bytesAsString() : "..."));
994
+ this.generateLabel();
995
+ this.needsVar[prefix] = true;
996
+ this.needsVar['context'] = true;
997
+ // set pc, activate new method, and return to main loop
998
+ // unless the method was a successfull primitive call (no context change)
999
+ this.source.push("vm.pc = ", this.pc);
1000
+ if (superSend === "directed") {
1001
+ this.source.push("; vm.sendSuperDirected(", prefix, num, suffix, ", ", numArgs, "); ");
1002
+ } else {
1003
+ this.source.push("; vm.send(", prefix, num, suffix, ", ", numArgs, ", ", superSend, "); ");
1004
+ }
1005
+ this.source.push("if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n");
1006
+ this.needsBreak = false; // already checked
1007
+ // need a label for coming back after send
1008
+ this.needsLabel[this.pc] = true;
1009
+ },
1010
+ generateClosureTemps: function(count, popValues) {
1011
+ if (this.debug) this.generateDebugCode("closure temps");
1012
+ this.generateLabel();
1013
+ this.needsVar['stack'] = true;
1014
+ this.source.push("var array = vm.instantiateClass(vm.specialObjects[7], ", count, ");\n");
1015
+ if (popValues) {
1016
+ for (var i = 0; i < count; i++)
1017
+ this.source.push("array.pointers[", i, "] = stack[vm.sp - ", count - i - 1, "];\n");
1018
+ this.source.push("stack[vm.sp -= ", count - 1, "] = array;\n");
1019
+ } else {
1020
+ this.source.push("stack[++vm.sp] = array;\n");
1021
+ }
1022
+ },
1023
+ generateClosureCopy: function(numArgs, numCopied, blockSize) {
1024
+ var from = this.pc,
1025
+ to = from + blockSize;
1026
+ if (this.debug) this.generateDebugCode("push closure(" + from + "-" + (to-1) + "): " + numCopied + " copied, " + numArgs + " args");
1027
+ this.generateLabel();
1028
+ this.needsVar['stack'] = true;
1029
+ this.source.push(
1030
+ "var closure = vm.instantiateClass(vm.specialObjects[36], ", numCopied, ");\n",
1031
+ "closure.pointers[0] = context; vm.reclaimableContextCount = 0;\n",
1032
+ "closure.pointers[1] = ", from + this.method.pointers.length * 4 + 1, ";\n", // encodeSqueakPC
1033
+ "closure.pointers[2] = ", numArgs, ";\n");
1034
+ if (numCopied > 0) {
1035
+ for (var i = 0; i < numCopied; i++)
1036
+ this.source.push("closure.pointers[", i + 3, "] = stack[vm.sp - ", numCopied - i - 1,"];\n");
1037
+ this.source.push("stack[vm.sp -= ", numCopied - 1,"] = closure;\n");
1038
+ } else {
1039
+ this.source.push("stack[++vm.sp] = closure;\n");
1040
+ }
1041
+ this.source.push("vm.pc = ", to, ";\n");
1042
+ if (this.singleStep) this.source.push("if (vm.breakOutOfInterpreter) return;\n");
1043
+ this.source.push("continue;\n");
1044
+ this.needsBreak = false; // already checked
1045
+ this.needsLabel[from] = true; // initial pc when activated
1046
+ this.needsLabel[to] = true; // for jump over closure
1047
+ if (to > this.endPC) this.endPC = to;
1048
+ },
1049
+ generatePushFullClosure: function(index, b3) {
1050
+ if (this.debug) this.generateDebugCode("push full closure " + (index + 1));
1051
+ this.generateLabel();
1052
+ this.needsVar['lit['] = true;
1053
+ this.needsVar['rcvr'] = true;
1054
+ this.needsVar['stack'] = true;
1055
+ var numCopied = b3 & 63;
1056
+ var outer;
1057
+ if ((b3 >> 6 & 1) === 1) {
1058
+ outer = "vm.nilObj";
1059
+ } else {
1060
+ outer = "context";
1061
+ }
1062
+ if ((b3 >> 7 & 1) === 1) {
1063
+ throw Error("on-stack receiver not yet supported");
1064
+ }
1065
+ this.source.push("var closure = vm.newFullClosure(", outer, ", ", numCopied, ", lit[", 1 + index, "]);\n");
1066
+ this.source.push("closure.pointers[", Squeak.ClosureFull_receiver, "] = rcvr;\n");
1067
+ if (outer === "context") this.source.push("vm.reclaimableContextCount = 0;\n");
1068
+ if (numCopied > 0) {
1069
+ for (var i = 0; i < numCopied; i++)
1070
+ this.source.push("closure.pointers[", i + Squeak.ClosureFull_firstCopiedValue, "] = stack[vm.sp - ", numCopied - i - 1,"];\n");
1071
+ this.source.push("stack[vm.sp -= ", numCopied - 1,"] = closure;\n");
1072
+ } else {
1073
+ this.source.push("stack[++vm.sp] = closure;\n");
1074
+ }
1075
+ },
1076
+ generateCallPrimitive: function(index, extendedStoreBytecode) {
1077
+ if (this.debug) this.generateDebugCode("call primitive " + index);
1078
+ this.generateLabel();
1079
+ if (this.method.bytes[this.pc] === extendedStoreBytecode) {
1080
+ this.needsVar['stack'] = true;
1081
+ this.source.push("if (vm.primFailCode) {stack[vm.sp] = vm.getErrorObjectFromPrimFailCode(); vm.primFailCode = 0;}\n");
1082
+ }
1083
+ },
1084
+ generateDirty: function(target, arg, suffix) {
1085
+ switch(target) {
1086
+ case "inst[": this.source.push("rcvr.dirty = true;\n"); break;
1087
+ case "lit[": this.source.push(target, arg, "].dirty = true;\n"); break;
1088
+ case "temp[": if (suffix !== "]") this.source.push(target, arg, "].dirty = true;\n"); break;
1089
+ default:
1090
+ throw Error("unexpected target " + target);
1091
+ }
1092
+ },
1093
+ generateLabel: function() {
1094
+ // remember label position for deleteUnneededLabels()
1095
+ if (this.prevPC) {
1096
+ this.sourceLabels[this.prevPC] = this.source.length;
1097
+ this.source.push("case ", this.prevPC, ":\n"); // must match deleteUnneededLabels
1098
+ }
1099
+ this.prevPC = this.pc;
1100
+ },
1101
+ generateDebugCode: function(command, what, arg1, suffix1, arg2, suffix2) {
1102
+ // single-step for previous instructiuon
1103
+ if (this.needsBreak) {
1104
+ this.source.push("if (vm.breakOutOfInterpreter) {vm.pc = ", this.prevPC, "; return}\n");
1105
+ this.needsLabel[this.prevPC] = true;
1106
+ }
1107
+ // comment for this instruction
1108
+ var bytecodes = [];
1109
+ for (var i = this.prevPC; i < this.pc; i++)
1110
+ bytecodes.push((this.method.bytes[i] + 0x100).toString(16).slice(-2).toUpperCase());
1111
+ this.source.push("// ", this.prevPC, " <", bytecodes.join(" "), "> ", command);
1112
+ // append argument to comment
1113
+ if (what !== undefined) {
1114
+ this.source.push(" ");
1115
+ switch (what) {
1116
+ case 'vm.nilObj': this.source.push('nil'); break;
1117
+ case 'vm.trueObj': this.source.push('true'); break;
1118
+ case 'vm.falseObj': this.source.push('false'); break;
1119
+ case 'rcvr': this.source.push('self'); break;
1120
+ case 'stack[vm.sp]': this.source.push('top of stack'); break;
1121
+ case 'inst[':
1122
+ if (!this.instVarNames) this.source.push('inst var ', arg1);
1123
+ else this.source.push(this.instVarNames[arg1]);
1124
+ break;
1125
+ case 'temp[':
1126
+ this.source.push('tmp', arg1 - 6);
1127
+ if (suffix1 !== ']') this.source.push('[', arg2, ']');
1128
+ break;
1129
+ case 'lit[':
1130
+ var lit = this.method.pointers[arg1];
1131
+ if (suffix1 === ']') this.source.push(lit);
1132
+ else this.source.push(lit.pointers[0].bytesAsString());
1133
+ break;
1134
+ default:
1135
+ this.source.push(what);
1136
+ }
1137
+ }
1138
+ this.source.push("\n");
1139
+ // enable single-step for next instruction
1140
+ this.needsBreak = this.singleStep;
1141
+ },
1142
+ generateInstruction: function(comment, instr) {
1143
+ if (this.debug) this.generateDebugCode(comment);
1144
+ this.generateLabel();
1145
+ this.source.push(instr, ";\n");
1146
+ },
1147
+ deleteUnneededLabels: function() {
1148
+ // switch statement is more efficient with fewer labels
1149
+ var hasAnyLabel = false;
1150
+ for (var i in this.sourceLabels)
1151
+ if (this.needsLabel[i])
1152
+ hasAnyLabel = true;
1153
+ else for (var j = 0; j < 3; j++)
1154
+ this.source[this.sourceLabels[i] + j] = "";
1155
+ if (!hasAnyLabel) {
1156
+ this.source[this.sourcePos['loop-start']] = "";
1157
+ this.source[this.sourcePos['loop-end']] = "";
1158
+ }
1159
+ },
1160
+ deleteUnneededVariables: function() {
1161
+ if (this.needsVar['stack']) this.needsVar['context'] = true;
1162
+ if (this.needsVar['inst[']) this.needsVar['rcvr'] = true;
1163
+ for (var i = 0; i < this.allVars.length; i++) {
1164
+ var v = this.allVars[i];
1165
+ if (!this.needsVar[v])
1166
+ this.source[this.sourcePos[v]] = "";
1167
+ }
1168
+ },
1169
+ });
lib/FileSaver.js ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* FileSaver.js
2
+ * A saveAs() FileSaver implementation.
3
+ * 1.3.8
4
+ * 2018-03-22 14:03:47
5
+ *
6
+ * By Eli Grey, https://eligrey.com
7
+ * License: MIT
8
+ * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
9
+ */
10
+
11
+ /*global self */
12
+ /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
13
+
14
+ /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */
15
+
16
+ (function(view) {
17
+ "use strict";
18
+ // IE <10 is explicitly unsupported
19
+ if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
20
+ return;
21
+ }
22
+ var
23
+ doc = view.document
24
+ // only get URL when necessary in case Blob.js hasn't overridden it yet
25
+ , get_URL = function() {
26
+ return view.URL || view.webkitURL || view;
27
+ }
28
+ , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
29
+ , can_use_save_link = "download" in save_link
30
+ , click = function(node) {
31
+ var event = new MouseEvent("click");
32
+ node.dispatchEvent(event);
33
+ }
34
+ , is_safari = /constructor/i.test(view.HTMLElement) || view.safari
35
+ , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
36
+ , setImmediate = view.setImmediate || view.setTimeout
37
+ , throw_outside = function(ex) {
38
+ setImmediate(function() {
39
+ throw ex;
40
+ }, 0);
41
+ }
42
+ , force_saveable_type = "application/octet-stream"
43
+ // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
44
+ , arbitrary_revoke_timeout = 1000 * 40 // in ms
45
+ , revoke = function(file) {
46
+ var revoker = function() {
47
+ if (typeof file === "string") { // file is an object URL
48
+ get_URL().revokeObjectURL(file);
49
+ } else { // file is a File
50
+ file.remove();
51
+ }
52
+ };
53
+ setTimeout(revoker, arbitrary_revoke_timeout);
54
+ }
55
+ , dispatch = function(filesaver, event_types, event) {
56
+ event_types = [].concat(event_types);
57
+ var i = event_types.length;
58
+ while (i--) {
59
+ var listener = filesaver["on" + event_types[i]];
60
+ if (typeof listener === "function") {
61
+ try {
62
+ listener.call(filesaver, event || filesaver);
63
+ } catch (ex) {
64
+ throw_outside(ex);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ , auto_bom = function(blob) {
70
+ // prepend BOM for UTF-8 XML and text/* types (including HTML)
71
+ // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
72
+ if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
73
+ return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
74
+ }
75
+ return blob;
76
+ }
77
+ , FileSaver = function(blob, name, no_auto_bom) {
78
+ if (!no_auto_bom) {
79
+ blob = auto_bom(blob);
80
+ }
81
+ // First try a.download, then web filesystem, then object URLs
82
+ var
83
+ filesaver = this
84
+ , type = blob.type
85
+ , force = type === force_saveable_type
86
+ , object_url
87
+ , dispatch_all = function() {
88
+ dispatch(filesaver, "writestart progress write writeend".split(" "));
89
+ }
90
+ // on any filesys errors revert to saving with object URLs
91
+ , fs_error = function() {
92
+ if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
93
+ // Safari doesn't allow downloading of blob urls
94
+ var reader = new FileReader();
95
+ reader.onloadend = function() {
96
+ var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
97
+ var popup = view.open(url, '_blank');
98
+ if(!popup) view.location.href = url;
99
+ url=undefined; // release reference before dispatching
100
+ filesaver.readyState = filesaver.DONE;
101
+ dispatch_all();
102
+ };
103
+ reader.readAsDataURL(blob);
104
+ filesaver.readyState = filesaver.INIT;
105
+ return;
106
+ }
107
+ // don't create more object URLs than needed
108
+ if (!object_url) {
109
+ object_url = get_URL().createObjectURL(blob);
110
+ }
111
+ if (force) {
112
+ view.location.href = object_url;
113
+ } else {
114
+ var opened = view.open(object_url, "_blank");
115
+ if (!opened) {
116
+ // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
117
+ view.location.href = object_url;
118
+ }
119
+ }
120
+ filesaver.readyState = filesaver.DONE;
121
+ dispatch_all();
122
+ revoke(object_url);
123
+ }
124
+ ;
125
+ filesaver.readyState = filesaver.INIT;
126
+
127
+ if (can_use_save_link) {
128
+ object_url = get_URL().createObjectURL(blob);
129
+ setImmediate(function() {
130
+ save_link.href = object_url;
131
+ save_link.download = name;
132
+ click(save_link);
133
+ dispatch_all();
134
+ revoke(object_url);
135
+ filesaver.readyState = filesaver.DONE;
136
+ }, 0);
137
+ return;
138
+ }
139
+
140
+ fs_error();
141
+ }
142
+ , FS_proto = FileSaver.prototype
143
+ , saveAs = function(blob, name, no_auto_bom) {
144
+ return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
145
+ }
146
+ ;
147
+
148
+ // IE 10+ (native saveAs)
149
+ if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
150
+ return function(blob, name, no_auto_bom) {
151
+ name = name || blob.name || "download";
152
+
153
+ if (!no_auto_bom) {
154
+ blob = auto_bom(blob);
155
+ }
156
+ return navigator.msSaveOrOpenBlob(blob, name);
157
+ };
158
+ }
159
+
160
+ // todo: detect chrome extensions & packaged apps
161
+ //save_link.target = "_blank";
162
+
163
+ FS_proto.abort = function(){};
164
+ FS_proto.readyState = FS_proto.INIT = 0;
165
+ FS_proto.WRITING = 1;
166
+ FS_proto.DONE = 2;
167
+
168
+ FS_proto.error =
169
+ FS_proto.onwritestart =
170
+ FS_proto.onprogress =
171
+ FS_proto.onwrite =
172
+ FS_proto.onabort =
173
+ FS_proto.onerror =
174
+ FS_proto.onwriteend =
175
+ null;
176
+
177
+ view.FileSaver_saveAs = saveAs;
178
+ }(
179
+ typeof self !== "undefined" && self
180
+ || typeof window !== "undefined" && window
181
+ || this
182
+ ));
lib/addtohomescreen.css ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .ath-viewport * {
2
+ -webkit-box-sizing: border-box;
3
+ -moz-box-sizing: border-box;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ .ath-viewport {
8
+ position: relative;
9
+ z-index: 2147483641;
10
+ pointer-events: none;
11
+
12
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
13
+ -webkit-touch-callout: none;
14
+ -webkit-user-select: none;
15
+ -moz-user-select: none;
16
+ -ms-user-select: none;
17
+ user-select: none;
18
+ -webkit-text-size-adjust: none;
19
+ -moz-text-size-adjust: none;
20
+ -ms-text-size-adjust: none;
21
+ -o-text-size-adjust: none;
22
+ text-size-adjust: none;
23
+ }
24
+
25
+ .ath-modal {
26
+ pointer-events: auto !important;
27
+ background: rgba(0,0,0,0.6);
28
+ }
29
+
30
+ .ath-mandatory {
31
+ background: #000;
32
+ }
33
+
34
+ .ath-container {
35
+ pointer-events: auto !important;
36
+ position: absolute;
37
+ z-index: 2147483641;
38
+ padding: 0.7em 0.6em;
39
+ width: 18em;
40
+
41
+ background: #eee;
42
+ background-size: 100% auto;
43
+
44
+ box-shadow: 0 0.2em 0 #d1d1d1;
45
+
46
+ font-family: sans-serif;
47
+ font-size: 15px;
48
+ line-height: 1.5em;
49
+ text-align: center;
50
+ }
51
+
52
+ .ath-container small {
53
+ font-size: 0.8em;
54
+ line-height: 1.3em;
55
+ display: block;
56
+ margin-top: 0.5em;
57
+ }
58
+
59
+ .ath-ios.ath-phone {
60
+ bottom: 1.8em;
61
+ left: 50%;
62
+ margin-left: -9em;
63
+ }
64
+
65
+ .ath-ios6.ath-tablet {
66
+ left: 5em;
67
+ top: 1.8em;
68
+ }
69
+
70
+ .ath-ios7.ath-tablet {
71
+ left: 0.7em;
72
+ top: 1.8em;
73
+ }
74
+
75
+ .ath-ios8.ath-tablet,
76
+ .ath-ios9.ath-tablet{
77
+ right: 0.4em;
78
+ top: 1.8em;
79
+ }
80
+
81
+ .ath-android {
82
+ bottom: 1.8em;
83
+ left: 50%;
84
+ margin-left: -9em;
85
+ }
86
+
87
+ /* close icon */
88
+ .ath-container:before {
89
+ content: '';
90
+ position: relative;
91
+ display: block;
92
+ float: right;
93
+ margin: -0.7em -0.6em 0 0.5em;
94
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAMAAABmmnOVAAAAdVBMVEUAAAA5OTkzMzM7Ozs3NzdBQUFAQEA/Pz8+Pj5BQUFAQEA/Pz8+Pj5BQUFAQEA/Pz9BQUE+Pj4/Pz8/Pz8+Pj4/Pz8/Pz8/Pz8+Pj4/Pz8+Pj4/Pz8/Pz8/Pz8/Pz8/Pz8+Pj4/Pz8/Pz8/Pz8/Pz9AQEA/Pz+fdCaPAAAAJnRSTlMACQoNDjM4OTo7PEFCQ0RFS6ytsbS1tru8vcTFxu7x8vX19vf4+C5yomAAAAJESURBVHgBvdzLTsJAGEfxr4C2KBcVkQsIDsK8/yPaqIsPzVlyzrKrX/5p0kkXEz81L23otc9NpIbbWia2YVLqdnhlqFlhGWpSDHe1aopsSIpRb8gK0dC3G30b9rVmhWZIimTICsvQtx/FsuYOrWHoDjX3Gu31gzJxdki934WrAIOsAIOsAIOiAMPhPsJTgKGN0BVsYIVsYIVpYIVpYIVpYIVpYIVpYIVpYIVpYIVlAIVgEBRs8BRs8BRs8BRs8BRs8BRs8BRs8BRTNmgKNngKNngKNngKNngKNhiKGxgiOlZoBlaYBlaYBlaYBlaYBlaYBlaYBlaYBlZIBlBMfQMrVAMr2KAqBENSHFHhGEABhi5CV6gGUKgGUKgGUKgGUFwuqgEUvoEVsoEVpoEUpgEUggF+gKTKY+h1fxSlC7/Z+RrxOQ3fcEoAPPHZBlaYBlaYBlaYBlZYBlYIhvLBCstw7PgM7hkiWOEZWGEaWGEaWGEaIsakEAysmHkGVpxmvoEVqoEVpoEVpoEVpoEVpoEVpoEVkoEVgkFQsEFSsEFQsGEcoSvY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnmbNAUT2c2WAo2eAo2eAo2eAo2eAo2eArNEPFACjZ4CjZ4CjZ4CjaIird/rBvFH6llNCvewdli1URWCIakSIZesUaDoFg36dKFWk9zCZDei3TtwmCj7pC22AwikiIZPEU29IpFNliKxa/hC9DFITjQPYhcAAAAAElFTkSuQmCC);
95
+ background-color: rgba(255,255,255,0.8);
96
+ background-size: 50%;
97
+ background-repeat: no-repeat;
98
+ background-position: 50%;
99
+ width: 2.7em;
100
+ height: 2.7em;
101
+ text-align: center;
102
+ overflow: hidden;
103
+ color: #a33;
104
+ z-index: 2147483642;
105
+ }
106
+
107
+ .ath-container.ath-icon:before {
108
+ position: absolute;
109
+ top: 0;
110
+ right: 0;
111
+ margin: 0;
112
+ float: none;
113
+ }
114
+
115
+ .ath-mandatory .ath-container:before {
116
+ display: none;
117
+ }
118
+
119
+ .ath-container.ath-android:before {
120
+ float: left;
121
+ margin: -0.7em 0.5em 0 -0.6em;
122
+ }
123
+
124
+ .ath-container.ath-android.ath-icon:before {
125
+ position: absolute;
126
+ right: auto;
127
+ left: 0;
128
+ margin: 0;
129
+ float: none;
130
+ }
131
+
132
+
133
+ /* applied only if the application icon is shown */
134
+ .ath-container.ath-icon {
135
+
136
+ }
137
+
138
+ .ath-action-icon {
139
+ display: inline-block;
140
+ vertical-align: middle;
141
+ background-position: 50%;
142
+ background-repeat: no-repeat;
143
+ text-indent: -9999em;
144
+ overflow: hidden;
145
+ }
146
+
147
+ .ath-ios7 .ath-action-icon,
148
+ .ath-ios8 .ath-action-icon,
149
+ .ath-ios9 .ath-action-icon{
150
+ width: 1.6em;
151
+ height: 1.6em;
152
+ background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAACtCAYAAAB7l7tOAAAF6UlEQVR4AezZWWxUZRiH8VcQEdxZEFFiUZBFUCIa1ABBDARDcCciYGKMqTEGww3SOcNSAwQTjOBiiIpEhRjAhRgXRC8MFxojEhAFZUGttVhaoSxlaW3n8W3yXZxm6vTrOMM5Q98n+V9MMu1pvl++uZhKuypghu49KaaTWGdZSYoVN6VD95nMpLNYZ9XNbdQR2od2k88O3Gm6Bh0t7H0p5Vwp2Ax3ajpu2tYbciFWwkTFO63DY6+JcI4USFaSyYpWp8N7SVZJKR3EinkBk9JxvZFXxhnZSjBaoWp1ZL0ES8WKYXMZp0AndORgy8WKFe5Yf1zvvSBWDEpys2LU6MjD5kmEWQlGKsJRHXlcqUSQVcItEnDEA6gAb7LhjvD9WO6yIEfICQI5A1nzGCYB1T4og5bBiFcyv2f6ujYhl4iVxwKG6qp8MK55HsqPwK0rMr9v/yEo3uCPrJstVh5KMER30Aeh31Ioq0FrHfjXw9CYghnrvYFTuqfEymFzGSwBlT4ARYr7u+K6GLmCVGvAGg2NMG0d/sgJnpScZLjXSkC5z8H3eQ72/k24Q8NfzvwFyK4qtuJSZKaubRPyE/K/Mtx+EvCHL+7uasId1t10w0scz/RzSzYzAfgKV30D3LPaG7lRkR8RK4tKKJKAMp+D7r0EfmmOe0x3m2itAc/ZxBjgAt1mXHWKPPkdb+QGSTJdrDaU5EoJ2OtzwD0WwY7KNNzbRfMFFg24WPdtGHnS221Cflgsj56hjwTs8TnY7oq7/QDhjutGicsb2AVcovsO18l6uPPNNiE/JFaGAq7Q7fY50G4LYVtz3FrdaNGyBXbIl+q24DqhyHes9EaulwR3SwtZs+ktAT/7HORliru1gnCndONFyx44Dfn7MPLYN7yR6yTJZAllJeguAT/4HOBFz8I3ZWm4E0TLFbBD7qn7EVdtHYx53R9ZN0ksrZRuErDN5+AuLIWvm+Oe1k0ULdfADrmX7idcR0/DyBXeyCdlLuMMOGCBz4F1ng+f7yFcve5e0fIFHELeiav6BAx70Rt5p0yhY3u/wR0kyarW/uX35b403PtFyzewQ75ctwtXzSkY8WqruHslSV8RscrL6TJ1bcvfWJ0/HzbtIdw/ugdFyzdwOOAq3T6fmzxwGQ3vbmO8iFioIWqYSsHMj9M/ljfuTsOdItoZBXYBfXX7cVXVwvXLm/8+fU3lcdCqdEMNGBbgUmRmfQISQKd5sGEn4VK6YtEiAXYBA3QVuA4q8hCHrDcafR1ul65jewfuovsCl7vJrNlOuEbdo6JFCuwCrtb9hqusBu56Cw4cI1y1briIWEBn3Ue0XKPuMdGiBg4H9NdV0HJ/6QZLOEPmPN0GmpfSPS5arIBdwHUtIFfoBsl/ZsgfhHCfFi2WwC5goO4AmvanbqBkzJA76tboZokWa2AXMEi3RTdAvDLkDqJFAhzB32xFD2wZsGXA0WfAlgFbBmwZsGXAlgFbBpzk04JaKb0iA9ZnF9x5SQAFtRKKIgPWZxfaeRmwAZ/BGbAB37eaG6MCbnq2Aed5czYyKirgpmcbsAHHZAZswN0Wwo7KeG1fFf2jAm56dtzOQ42yB+65mDhWFBUwUETMUiMDNmADbp/APRaTAh6I2bpGCNw1bufRZJQ1cPdF/NueHZsgDEBBGLbMGoIu4AZu5gLOZeEaYmEXeznF3jRPyEv4frgJvvJe3qTefY0AAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwb8rwADBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgz4/sz1Nia/9hizA7zgklwy3RYwYMBzBRjw4bPjxAbAAizAAtwgwAIswAIswAIMGDBgARZgARZgAS4FWIAFWIAFWIABAwYswAIswAIswIUAC7AAC7AACzBgwIAFWIAFWIAFuBBgARZgARZgAQYMGPApQ99ZCdgWtzqwATbABtgAG2DbnxNb7zbRimsMLMACrDf2wMWI/WasfQAAAABJRU5ErkJggg==);
153
+ margin-top: -0.3em;
154
+ background-size: auto 100%;
155
+ }
156
+
157
+ .ath-ios6 .ath-action-icon {
158
+ width: 1.8em;
159
+ height: 1.8em;
160
+ background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAB0CAQAAADAmnOnAAAAAnNCSVQICFXsRgQAAAAJcEhZcwAAWwEAAFsBAXkZiFwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAF4klEQVR4Ae3a/a+XdR3H8ec5HM45HDmKICoVohkZsxESRRCzcZM/2JKkdGR5MrSkleA0Pd00O4u5IVuNM2yYc6XSzCExU4oUNRPCJFdMUAhsYZpUGhscOHA4N8/WZzsL6HBxvofvdV3fa3yer//gsV3vH659KHzncBsJxUYhDzOEhCKQbORs+ip2wzgM+wvj+P9i35qAGLaHGcQSgKSTrxBLABJppZpYApCspoFYApBsZjSxBCD5OxOJJQBJG1cQSwCSLpqJJQCJ3MvgCGTinuSMCJS8LZwfgZL3FtMiUPIOcU0ESl4PLRHoRPsJtREoeRsYGYGS9yrvo6RmpbLaigWSfzOdErLs6+bLUMFA0sF1+QF1cz1UNlBYK9V5AHXyWSgEkKyiIWOgGh829Ki1lLcaxjCVK7mJRSxjBY+zgRf/u9pXcMB7jhEZAg32EUP3O6hMKOP5Iq2sZQeHMZXt5KKMgOpcY+iHVnFyjeQKlrCBdsxge5ieAVC9vzLUelI8H+A7bKIHM10H81IGGuKvDf1ggDxVTKOV1zG3/Yia1ICG+ltD32MgNTKfP2HuW0VDKkCNrjfUTOm9i6XswwrZJkaVHeh0f2fodkrtfO6jAytqrzG+rEDDfVG1x1sprZEs5RBW4PZxeT+Bbrf5hPu9arfzKaU6WjiAFbseWvoF1GW/6vYGSmkyW7Dit4xB5QHq9Br6Xx2t9GAhtp6zkoHsfNp1J9wX6H+jeR4LtJc4LxGopZZyNpN/YcG2mw9nBTSPLizgOmjKAujGgvJID3ekD7QYi7nGzkvmQtpA38Vi7iJf0TedlC7QTVjMfcY2QyvSBPpUMW/PIBfbo9pls1XpAX2EdizeznStob3OJpQO0DB2YfE21q2GtnghpAm0Gou3T9tm6BGHQppA12HRVt17eboNlydNoLHsx2JtmL801OYcQmkC/QKLtQt9ydBW3wNpA30ci7Ur3WdolUMhbaBqNhf/8qQJ9Hkszs5wjaH9XkUobaAqtmFRdoGbDb3sWMgG6DIs5852knO82RaXer+P+qyb3eWeo7ZNBrRZvm1otY2QFdBjeHIb6hTne49Put12+9ObMoDdYmfy5UkF6AK6cCCr9aM2u9IddptcOYCG+FNDB5xLKCugO7G01TndFp/xgAntdYvrfdwVLnORt3q9Vx25F27DUjbGPxr6qxMgW6Cd2N+d6wLXedA+6nKbK73Lr/pJxzusvE/wZrvX0FOOgGyBxmF/dprXutYOj6nNdS6xyYnWp/dGcaGdhr5vDWQN9E1MXrUzfcA2j2qPj/l1J1uT9iPOeh8w1O7nCGUN9HzyGZ7ndo9qp0ucanU2r1xH+wdDu5wIeQDVVx0+/kd1i697RNv8thdn+Qz4Uv9p6DeOhHyApmBfq3OBu+3Nfd7nVELZAX3Nw4ZarYG8gG7GY1dlk6/Zm3/2Rk8jlB1QvT82dNAmQjkBVf8Mj957fdrefM7ZVhPKEuidvmDob06CXIGGbsX/bZDf8KAhfdbJhLIGmuZuQ084HHIGatiLvRvrRkP6qldbBXkAzbfD0N0OhryBGqrEMOd50FC7d1hPKGugBh8ydMh5hPIGGouI1d5lj6F1vptQ9kDvcKOhN5wMlQH0QcRGnzC03yZCeQDN9G1D6xwBFQI07FI8x02GdjgB8gJqttPQcmuhYoAumzvG7YZWejrkA1TrPYYO+SVCFQO0aM4bqj0uJJQH0LluSP7PkyeQU9QOmyAvoBm+Zegpz4LKA/qYB/wE5AXUe3m81zqoRKAPOYWcuvP9dxvqcD6h7IAKkaNU3eUlHLcI9EzS5YlAi62h/zUy89QCqqKUmvgHywsJlEHnsQYxAvXVIJo5gIhnPhiBju1iNmLvLn85Ah1ZPYs5jBGo72awEzEC9dVwHqQHI9DxWoAYgSLQQKteGIESu/qhCJTYtT+PQBEoAkWgCBSBkotAEehUWwSKQBEoAkWg/BeBIlAEikARKAJFoFmealu4gVLy1Gt5dkARKAL9BzujPSurTmu/AAAAAElFTkSuQmCC);
161
+ margin-bottom: 0.4em;
162
+ background-size: 100% auto;
163
+ }
164
+
165
+ .ath-android .ath-action-icon {
166
+ width: 1.4em;
167
+ height: 1.5em;
168
+ background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAANlBMVEVmZmb///9mZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZW6fJrAAAAEXRSTlMAAAYHG21ub8fLz9DR8/T4+RrZ9owAAAB3SURBVHja7dNLDoAgDATQWv4gKve/rEajJOJiWLgg6WzpSyB0aHqHiNj6nL1lovb4C+hYzkSNAT7mryQFAVOeGAj4CjwEtgrWXpD/uZKtwEJApXt+Vn0flzRhgNiFZQkOXY0aADQZCOCPlsZJ46Rx0jhp3IiN2wGDHhxtldrlwQAAAABJRU5ErkJggg==);
169
+ background-size: 100% auto;
170
+ }
171
+
172
+ .ath-container p {
173
+ margin: 0;
174
+ padding: 0;
175
+ position: relative;
176
+ z-index: 2147483642;
177
+ text-shadow: 0 0.1em 0 #fff;
178
+ font-size: 1.1em;
179
+ }
180
+
181
+ .ath-ios.ath-phone:after {
182
+ content: '';
183
+ background: #eee;
184
+ position: absolute;
185
+ width: 2em;
186
+ height: 2em;
187
+ bottom: -0.9em;
188
+ left: 50%;
189
+ margin-left: -1em;
190
+ -webkit-transform: scaleX(0.9) rotate(45deg);
191
+ transform: scaleX(0.9) rotate(45deg);
192
+ box-shadow: 0.2em 0.2em 0 #d1d1d1;
193
+ }
194
+
195
+ .ath-ios.ath-tablet:after {
196
+ content: '';
197
+ background: #eee;
198
+ position: absolute;
199
+ width: 2em;
200
+ height: 2em;
201
+ top: -0.9em;
202
+ left: 50%;
203
+ margin-left: -1em;
204
+ -webkit-transform: scaleX(0.9) rotate(45deg);
205
+ transform: scaleX(0.9) rotate(45deg);
206
+ z-index: 2147483641;
207
+ }
208
+
209
+ .ath-application-icon {
210
+ position: relative;
211
+ padding: 0;
212
+ border: 0;
213
+ margin: 0 auto 0.2em auto;
214
+ height: 6em;
215
+ width: 6em;
216
+ z-index: 2147483642;
217
+ }
218
+
219
+ .ath-container.ath-ios .ath-application-icon {
220
+ border-radius: 1em;
221
+ box-shadow: 0 0.2em 0.4em rgba(0,0,0,0.3),
222
+ inset 0 0.07em 0 rgba(255,255,255,0.5);
223
+ margin: 0 auto 0.4em auto;
224
+ }
225
+
226
+ @media only screen and (orientation: landscape) {
227
+ .ath-container.ath-phone {
228
+ width: 24em;
229
+ }
230
+
231
+ .ath-android.ath-phone {
232
+ margin-left: -12em;
233
+ }
234
+
235
+ .ath-ios.ath-phone {
236
+ margin-left: -12em;
237
+ }
238
+
239
+ .ath-ios6:after {
240
+ left: 39%;
241
+ }
242
+
243
+ .ath-ios8.ath-phone {
244
+ left: auto;
245
+ bottom: auto;
246
+ right: 0.4em;
247
+ top: 1.8em;
248
+ }
249
+
250
+ .ath-ios8.ath-phone:after {
251
+ bottom: auto;
252
+ top: -0.9em;
253
+ left: 68%;
254
+ z-index: 2147483641;
255
+ box-shadow: none;
256
+ }
257
+ }
lib/addtohomescreen.js ADDED
@@ -0,0 +1,725 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Add to Homescreen v3.2.2 ~ (c) 2015 Matteo Spinelli ~ @license: http://cubiq.org/license */
2
+ (function (window, document) {
3
+ /*
4
+ _ _ _____ _____
5
+ ___ _| |_| |_ _|___| | |___ _____ ___ ___ ___ ___ ___ ___ ___
6
+ | .'| . | . | | | | . | | . | | -_|_ -| _| _| -_| -_| |
7
+ |__,|___|___| |_| |___|__|__|___|_|_|_|___|___|___|_| |___|___|_|_|
8
+ by Matteo Spinelli ~ http://cubiq.org
9
+ */
10
+
11
+ // Check for addEventListener browser support (prevent errors in IE<9)
12
+ var _eventListener = 'addEventListener' in window;
13
+
14
+ // Check if document is loaded, needed by autostart
15
+ var _DOMReady = false;
16
+ if ( document.readyState === 'complete' ) {
17
+ _DOMReady = true;
18
+ } else if ( _eventListener ) {
19
+ window.addEventListener('load', loaded, false);
20
+ }
21
+
22
+ function loaded () {
23
+ window.removeEventListener('load', loaded, false);
24
+ _DOMReady = true;
25
+ }
26
+
27
+ // regex used to detect if app has been added to the homescreen
28
+ var _reSmartURL = /\/ath(\/)?$/;
29
+ var _reQueryString = /([\?&]ath=[^&]*$|&ath=[^&]*(&))/;
30
+
31
+ // singleton
32
+ var _instance;
33
+ function ath (options) {
34
+ _instance = _instance || new ath.Class(options);
35
+
36
+ return _instance;
37
+ }
38
+
39
+ // message in all supported languages
40
+ ath.intl = {
41
+ de_de: {
42
+ ios: 'Um diese Web-App zum Home-Bildschirm hinzuzufügen, tippen Sie auf %icon und dann <strong>Zum Home-Bildschirm</strong>.',
43
+ android: 'Um diese Web-App zum Home-Bildschirm hinzuzufügen, öffnen Sie das Menü und tippen dann auf <strong>Zum Startbildschirm hinzufügen</strong>. <small>Wenn Ihr Gerät eine Menütaste hat, lässt sich das Browsermenü über diese öffnen. Ansonsten tippen Sie auf %icon.</small>'
44
+ },
45
+
46
+ da_dk: {
47
+ ios: 'For at tilføje denne web app til hjemmeskærmen: Tryk %icon og derefter <strong>Føj til hjemmeskærm</strong>.',
48
+ android: 'For at tilføje denne web app til hjemmeskærmen, åbn browser egenskaber menuen og tryk på <strong>Føj til hjemmeskærm</strong>. <small>Denne menu kan tilgås ved at trykke på menu knappen, hvis din enhed har en, eller ved at trykke på det øverste højre menu ikon %icon.</small>'
49
+ },
50
+
51
+ en_us: {
52
+ ios: 'To add this web app to the home screen: tap %icon and then <strong>Add to Home Screen</strong>.',
53
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
54
+ },
55
+
56
+ es_es: {
57
+ ios: 'Para añadir esta aplicación web a la pantalla de inicio: pulsa %icon y selecciona <strong>Añadir a pantalla de inicio</strong>.',
58
+ android: 'Para añadir esta aplicación web a la pantalla de inicio, abre las opciones y pulsa <strong>Añadir a pantalla inicio</strong>. <small>El menú se puede acceder pulsando el botón táctil en caso de tenerlo, o bien el icono de la parte superior derecha de la pantalla %icon.</small>'
59
+ },
60
+
61
+ fi_fi: {
62
+ ios: 'Liitä tämä sovellus kotivalikkoon: klikkaa %icon ja tämän jälkeen <strong>Lisää kotivalikkoon</strong>.',
63
+ android: 'Lisätäksesi tämän sovelluksen aloitusnäytölle, avaa selaimen valikko ja klikkaa tähti -ikonia tai <strong>Lisää aloitusnäytölle tekstiä</strong>. <small>Valikkoon pääsee myös painamalla menuvalikkoa, jos laitteessasi on sellainen tai koskettamalla oikealla yläkulmassa menu ikonia %icon.</small>'
64
+ },
65
+
66
+ fr_fr: {
67
+ ios: 'Pour ajouter cette application web sur l\'écran d\'accueil : Appuyez %icon et sélectionnez <strong>Ajouter sur l\'écran d\'accueil</strong>.',
68
+ android: 'Pour ajouter cette application web sur l\'écran d\'accueil : Appuyez sur le bouton "menu", puis sur <strong>Ajouter sur l\'écran d\'accueil</strong>. <small>Le menu peut-être accessible en appyant sur le bouton "menu" du téléphone s\'il en possède un <i class="fa fa-bars"></i>. Sinon, il se trouve probablement dans la coin supérieur droit du navigateur %icon.</small>'
69
+ },
70
+
71
+ he_il: {
72
+ ios: '<span dir="rtl">להוספת האפליקציה למסך הבית: ללחוץ על %icon ואז <strong>הוסף למסך הבית</strong>.</span>',
73
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
74
+ },
75
+
76
+ hu_hu: {
77
+ ios: 'Ha hozzá szeretné adni ezt az alkalmazást a kezdőképernyőjéhez, érintse meg a következő ikont: %icon , majd a <strong>Hozzáadás a kezdőképernyőhöz</strong> menüpontot.',
78
+ android: 'Ha hozzá szeretné adni ezt az alkalmazást a kezdőképernyőjéhez, a böngésző menüjében kattintson a <strong>Hozzáadás a kezdőképernyőhöz</strong> menüpontra. <small>A böngésző menüjét a következő ikon megérintésével tudja megnyitni: %icon.</small>'
79
+ },
80
+
81
+ it_it: {
82
+ ios: 'Per aggiungere questa web app alla schermata iniziale: premi %icon e poi <strong>Aggiungi a Home</strong>.',
83
+ android: 'Per aggiungere questa web app alla schermata iniziale, apri il menu opzioni del browser e premi su <strong>Aggiungi alla homescreen</strong>. <small>Puoi accedere al menu premendo il pulsante hardware delle opzioni se la tua device ne ha uno, oppure premendo l\'icona %icon in alto a destra.</small>'
84
+ },
85
+
86
+ ja_jp: {
87
+ ios: 'このウェプアプリをホーム画面に追加するために%iconを押して<strong>ホーム画面に追加</strong>。',
88
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
89
+ },
90
+
91
+ ko_kr: {
92
+ ios: '홈 화면에 바로가기 생성: %icon 을 클릭한 후 <strong>홈 화면에 추가</strong>.',
93
+ android: '브라우저 옵션 메뉴의 <string>홈 화면에 추가</string>를 클릭하여 홈화면에 바로가기를 생성할 수 있습니다. <small>옵션 메뉴는 장치의 메뉴 버튼을 누르거나 오른쪽 상단의 메뉴 아이콘 %icon을 클릭하여 접근할 수 있습니다.</small>'
94
+ },
95
+
96
+ nb_no: {
97
+ ios: 'For å installere denne appen på hjem-skjermen: trykk på %icon og deretter <strong>Legg til på Hjem-skjerm</strong>.',
98
+ android: 'For å legge til denne webappen på startsiden åpner en nettlesermenyen og velger <strong>Legg til på startsiden</strong>. <small>Menyen åpnes ved å trykke på den fysiske menyknappen hvis enheten har det, eller ved å trykke på menyikonet øverst til høyre %icon.</small>'
99
+ },
100
+
101
+ pt_br: {
102
+ ios: 'Para adicionar este app à tela de início: clique %icon e então <strong>Tela de início</strong>.',
103
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
104
+ },
105
+
106
+ pt_pt: {
107
+ ios: 'Para adicionar esta app ao ecrã principal: clique %icon e depois <strong>Ecrã principal</strong>.',
108
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
109
+ },
110
+
111
+ nl_nl: {
112
+ ios: 'Om deze webapp op je telefoon te installeren, klik op %icon en dan <strong>Zet in beginscherm</strong>.',
113
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
114
+ },
115
+
116
+ ru_ru: {
117
+ ios: 'Чтобы добавить этот сайт на свой домашний экран, нажмите на иконку %icon и затем <strong>На экран "Домой"</strong>.',
118
+ android: 'Чтобы добавить сайт на свой домашний экран, откройте меню браузера и нажмите на <strong>Добавить на главный экран</strong>. <small>Меню можно вызвать, нажав на кнопку меню вашего телефона, если она есть. Или найдите иконку сверху справа %icon[иконка].</small>'
119
+ },
120
+
121
+ sv_se: {
122
+ ios: 'För att lägga till denna webbapplikation på hemskärmen: tryck på %icon och därefter <strong>Lägg till på hemskärmen</strong>.',
123
+ android: 'För att lägga till den här webbappen på hemskärmen öppnar du webbläsarens alternativ-meny och väljer <strong>Lägg till på startskärmen</strong>. <small>Man hittar menyn genom att trycka på hårdvaruknappen om din enhet har en sådan, eller genom att trycka på menyikonen högst upp till höger %icon.</small>'
124
+ },
125
+
126
+ zh_cn: {
127
+ ios: '如要把应用程序加至主屏幕,请点击%icon, 然后<strong>添加到主屏幕</strong>',
128
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
129
+ },
130
+
131
+ zh_tw: {
132
+ ios: '如要把應用程式加至主屏幕, 請點擊%icon, 然後<strong>加至主屏幕</strong>.',
133
+ android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon %icon.</small>'
134
+ }
135
+ };
136
+
137
+ // Add 2 characters language support (Android mostly)
138
+ for ( var lang in ath.intl ) {
139
+ ath.intl[lang.substr(0, 2)] = ath.intl[lang];
140
+ }
141
+
142
+ // default options
143
+ ath.defaults = {
144
+ appID: 'org.cubiq.addtohome', // local storage name (no need to change)
145
+ fontSize: 15, // base font size, used to properly resize the popup based on viewport scale factor
146
+ debug: false, // override browser checks
147
+ logging: false, // log reasons for showing or not showing to js console; defaults to true when debug is true
148
+ modal: false, // prevent further actions until the message is closed
149
+ mandatory: false, // you can't proceed if you don't add the app to the homescreen
150
+ autostart: true, // show the message automatically
151
+ skipFirstVisit: false, // show only to returning visitors (ie: skip the first time you visit)
152
+ startDelay: 1, // display the message after that many seconds from page load
153
+ lifespan: 15, // life of the message in seconds
154
+ displayPace: 1440, // minutes before the message is shown again (0: display every time, default 24 hours)
155
+ maxDisplayCount: 0, // absolute maximum number of times the message will be shown to the user (0: no limit)
156
+ icon: true, // add touch icon to the message
157
+ message: '', // the message can be customized
158
+ validLocation: [], // list of pages where the message will be shown (array of regexes)
159
+ onInit: null, // executed on instance creation
160
+ onShow: null, // executed when the message is shown
161
+ onRemove: null, // executed when the message is removed
162
+ onAdd: null, // when the application is launched the first time from the homescreen (guesstimate)
163
+ onPrivate: null, // executed if user is in private mode
164
+ privateModeOverride: false, // show the message even in private mode (very rude)
165
+ detectHomescreen: false // try to detect if the site has been added to the homescreen (false | true | 'hash' | 'queryString' | 'smartURL')
166
+ };
167
+
168
+ // browser info and capability
169
+ var _ua = window.navigator.userAgent;
170
+
171
+ var _nav = window.navigator;
172
+ _extend(ath, {
173
+ hasToken: document.location.hash == '#ath' || _reSmartURL.test(document.location.href) || _reQueryString.test(document.location.search),
174
+ isRetina: window.devicePixelRatio && window.devicePixelRatio > 1,
175
+ isIDevice: (/iphone|ipod|ipad/i).test(_ua),
176
+ isMobileChrome: _ua.indexOf('Android') > -1 && (/Chrome\/[.0-9]*/).test(_ua) && _ua.indexOf("Version") == -1,
177
+ isMobileIE: _ua.indexOf('Windows Phone') > -1,
178
+ language: _nav.language && _nav.language.toLowerCase().replace('-', '_') || ''
179
+ });
180
+
181
+ // falls back to en_us if language is unsupported
182
+ ath.language = ath.language && ath.language in ath.intl ? ath.language : 'en_us';
183
+
184
+ ath.isMobileSafari = ath.isIDevice && _ua.indexOf('Safari') > -1 && _ua.indexOf('CriOS') < 0;
185
+ ath.OS = ath.isIDevice ? 'ios' : ath.isMobileChrome ? 'android' : ath.isMobileIE ? 'windows' : 'unsupported';
186
+
187
+ ath.OSVersion = _ua.match(/(OS|Android) (\d+[_\.]\d+)/);
188
+ ath.OSVersion = ath.OSVersion && ath.OSVersion[2] ? +ath.OSVersion[2].replace('_', '.') : 0;
189
+
190
+ ath.isStandalone = 'standalone' in window.navigator && window.navigator.standalone;
191
+ ath.isTablet = (ath.isMobileSafari && _ua.indexOf('iPad') > -1) || (ath.isMobileChrome && _ua.indexOf('Mobile') < 0);
192
+
193
+ ath.isCompatible = (ath.isMobileSafari && ath.OSVersion >= 6) || ath.isMobileChrome; // TODO: add winphone
194
+
195
+ var _defaultSession = {
196
+ lastDisplayTime: 0, // last time we displayed the message
197
+ returningVisitor: false, // is this the first time you visit
198
+ displayCount: 0, // number of times the message has been shown
199
+ optedout: false, // has the user opted out
200
+ added: false // has been actually added to the homescreen
201
+ };
202
+
203
+ ath.removeSession = function (appID) {
204
+ try {
205
+ if (!localStorage) {
206
+ throw new Error('localStorage is not defined');
207
+ }
208
+
209
+ localStorage.removeItem(appID || ath.defaults.appID);
210
+ } catch (e) {
211
+ // we are most likely in private mode
212
+ }
213
+ };
214
+
215
+ ath.doLog = function (logStr) {
216
+ if ( this.options.logging ) {
217
+ console.log(logStr);
218
+ }
219
+ };
220
+
221
+ ath.Class = function (options) {
222
+ // class methods
223
+ this.doLog = ath.doLog;
224
+
225
+ // merge default options with user config
226
+ this.options = _extend({}, ath.defaults);
227
+ _extend(this.options, options);
228
+ // override defaults that are dependent on each other
229
+ if ( options && options.debug && (typeof options.logging === "undefined") ) {
230
+ this.options.logging = true;
231
+ }
232
+
233
+ // IE<9 so exit (I hate you, really)
234
+ if ( !_eventListener ) {
235
+ return;
236
+ }
237
+
238
+ // normalize some options
239
+ this.options.mandatory = this.options.mandatory && ( 'standalone' in window.navigator || this.options.debug );
240
+ this.options.modal = this.options.modal || this.options.mandatory;
241
+ if ( this.options.mandatory ) {
242
+ this.options.startDelay = -0.5; // make the popup hasty
243
+ }
244
+ this.options.detectHomescreen = this.options.detectHomescreen === true ? 'hash' : this.options.detectHomescreen;
245
+
246
+ // setup the debug environment
247
+ if ( this.options.debug ) {
248
+ ath.isCompatible = true;
249
+ ath.OS = typeof this.options.debug == 'string' ? this.options.debug : ath.OS == 'unsupported' ? 'android' : ath.OS;
250
+ ath.OSVersion = ath.OS == 'ios' ? '8' : '4';
251
+ }
252
+
253
+ // the element the message will be appended to
254
+ this.container = document.documentElement;
255
+
256
+ // load session
257
+ this.session = this.getItem(this.options.appID);
258
+ this.session = this.session ? JSON.parse(this.session) : undefined;
259
+
260
+ // user most likely came from a direct link containing our token, we don't need it and we remove it
261
+ if ( ath.hasToken && ( !ath.isCompatible || !this.session ) ) {
262
+ ath.hasToken = false;
263
+ _removeToken();
264
+ }
265
+
266
+ // the device is not supported
267
+ if ( !ath.isCompatible ) {
268
+ this.doLog("Add to homescreen: not displaying callout because device not supported");
269
+ return;
270
+ }
271
+
272
+ this.session = this.session || _defaultSession;
273
+
274
+ // check if we can use the local storage
275
+ try {
276
+ if (!localStorage) {
277
+ throw new Error('localStorage is not defined');
278
+ }
279
+
280
+ localStorage.setItem(this.options.appID, JSON.stringify(this.session));
281
+ ath.hasLocalStorage = true;
282
+ } catch (e) {
283
+ // we are most likely in private mode
284
+ ath.hasLocalStorage = false;
285
+
286
+ if ( this.options.onPrivate ) {
287
+ this.options.onPrivate.call(this);
288
+ }
289
+ }
290
+
291
+ // check if this is a valid location
292
+ var isValidLocation = !this.options.validLocation.length;
293
+ for ( var i = this.options.validLocation.length; i--; ) {
294
+ if ( this.options.validLocation[i].test(document.location.href) ) {
295
+ isValidLocation = true;
296
+ break;
297
+ }
298
+ }
299
+
300
+ // check compatibility with old versions of add to homescreen. Opt-out if an old session is found
301
+ if ( this.getItem('addToHome') ) {
302
+ this.optOut();
303
+ }
304
+
305
+ // critical errors:
306
+ if ( this.session.optedout ) {
307
+ this.doLog("Add to homescreen: not displaying callout because user opted out");
308
+ return;
309
+ }
310
+ if ( this.session.added ) {
311
+ this.doLog("Add to homescreen: not displaying callout because already added to the homescreen");
312
+ return;
313
+ }
314
+ if ( !isValidLocation ) {
315
+ this.doLog("Add to homescreen: not displaying callout because not a valid location");
316
+ return;
317
+ }
318
+
319
+ // check if the app is in stand alone mode
320
+ if ( ath.isStandalone ) {
321
+ // execute the onAdd event if we haven't already
322
+ if ( !this.session.added ) {
323
+ this.session.added = true;
324
+ this.updateSession();
325
+
326
+ if ( this.options.onAdd && ath.hasLocalStorage ) { // double check on localstorage to avoid multiple calls to the custom event
327
+ this.options.onAdd.call(this);
328
+ }
329
+ }
330
+
331
+ this.doLog("Add to homescreen: not displaying callout because in standalone mode");
332
+ return;
333
+ }
334
+
335
+ // (try to) check if the page has been added to the homescreen
336
+ if ( this.options.detectHomescreen ) {
337
+ // the URL has the token, we are likely coming from the homescreen
338
+ if ( ath.hasToken ) {
339
+ _removeToken(); // we don't actually need the token anymore, we remove it to prevent redistribution
340
+
341
+ // this is called the first time the user opens the app from the homescreen
342
+ if ( !this.session.added ) {
343
+ this.session.added = true;
344
+ this.updateSession();
345
+
346
+ if ( this.options.onAdd && ath.hasLocalStorage ) { // double check on localstorage to avoid multiple calls to the custom event
347
+ this.options.onAdd.call(this);
348
+ }
349
+ }
350
+
351
+ this.doLog("Add to homescreen: not displaying callout because URL has token, so we are likely coming from homescreen");
352
+ return;
353
+ }
354
+
355
+ // URL doesn't have the token, so add it
356
+ if ( this.options.detectHomescreen == 'hash' ) {
357
+ history.replaceState('', window.document.title, document.location.href + '#ath');
358
+ } else if ( this.options.detectHomescreen == 'smartURL' ) {
359
+ history.replaceState('', window.document.title, document.location.href.replace(/(\/)?$/, '/ath$1'));
360
+ } else {
361
+ history.replaceState('', window.document.title, document.location.href + (document.location.search ? '&' : '?' ) + 'ath=');
362
+ }
363
+ }
364
+
365
+ // check if this is a returning visitor
366
+ if ( !this.session.returningVisitor ) {
367
+ this.session.returningVisitor = true;
368
+ this.updateSession();
369
+
370
+ // we do not show the message if this is your first visit
371
+ if ( this.options.skipFirstVisit ) {
372
+ this.doLog("Add to homescreen: not displaying callout because skipping first visit");
373
+ return;
374
+ }
375
+ }
376
+
377
+ // we do no show the message in private mode
378
+ if ( !this.options.privateModeOverride && !ath.hasLocalStorage ) {
379
+ this.doLog("Add to homescreen: not displaying callout because browser is in private mode");
380
+ return;
381
+ }
382
+
383
+ // all checks passed, ready to display
384
+ this.ready = true;
385
+
386
+ if ( this.options.onInit ) {
387
+ this.options.onInit.call(this);
388
+ }
389
+
390
+ if ( this.options.autostart ) {
391
+ this.doLog("Add to homescreen: autostart displaying callout");
392
+ this.show();
393
+ }
394
+ };
395
+
396
+ ath.Class.prototype = {
397
+ // event type to method conversion
398
+ events: {
399
+ load: '_delayedShow',
400
+ error: '_delayedShow',
401
+ orientationchange: 'resize',
402
+ resize: 'resize',
403
+ scroll: 'resize',
404
+ click: 'remove',
405
+ touchmove: '_preventDefault',
406
+ transitionend: '_removeElements',
407
+ webkitTransitionEnd: '_removeElements',
408
+ MSTransitionEnd: '_removeElements'
409
+ },
410
+
411
+ handleEvent: function (e) {
412
+ var type = this.events[e.type];
413
+ if ( type ) {
414
+ this[type](e);
415
+ }
416
+ },
417
+
418
+ show: function (force) {
419
+ // in autostart mode wait for the document to be ready
420
+ if ( this.options.autostart && !_DOMReady ) {
421
+ setTimeout(this.show.bind(this), 50);
422
+ // we are not displaying callout because DOM not ready, but don't log that because
423
+ // it would log too frequently
424
+ return;
425
+ }
426
+
427
+ // message already on screen
428
+ if ( this.shown ) {
429
+ this.doLog("Add to homescreen: not displaying callout because already shown on screen");
430
+ return;
431
+ }
432
+
433
+ var now = Date.now();
434
+ var lastDisplayTime = this.session.lastDisplayTime;
435
+
436
+ if ( force !== true ) {
437
+ // this is needed if autostart is disabled and you programmatically call the show() method
438
+ if ( !this.ready ) {
439
+ this.doLog("Add to homescreen: not displaying callout because not ready");
440
+ return;
441
+ }
442
+
443
+ // we obey the display pace (prevent the message to popup too often)
444
+ if ( now - lastDisplayTime < this.options.displayPace * 60000 ) {
445
+ this.doLog("Add to homescreen: not displaying callout because displayed recently");
446
+ return;
447
+ }
448
+
449
+ // obey the maximum number of display count
450
+ if ( this.options.maxDisplayCount && this.session.displayCount >= this.options.maxDisplayCount ) {
451
+ this.doLog("Add to homescreen: not displaying callout because displayed too many times already");
452
+ return;
453
+ }
454
+ }
455
+
456
+ this.shown = true;
457
+
458
+ // increment the display count
459
+ this.session.lastDisplayTime = now;
460
+ this.session.displayCount++;
461
+ this.updateSession();
462
+
463
+ // try to get the highest resolution application icon
464
+ if ( !this.applicationIcon ) {
465
+ if ( ath.OS == 'ios' ) {
466
+ this.applicationIcon = document.querySelector('head link[rel^=apple-touch-icon][sizes="152x152"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon][sizes="120x120"],head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon]');
467
+ } else {
468
+ this.applicationIcon = document.querySelector('head link[rel^="shortcut icon"][sizes="196x196"],head link[rel^=apple-touch-icon]');
469
+ }
470
+ }
471
+
472
+ var message = '';
473
+
474
+ if ( typeof this.options.message == 'object' && ath.language in this.options.message ) { // use custom language message
475
+ message = this.options.message[ath.language][ath.OS];
476
+ } else if ( typeof this.options.message == 'object' && ath.OS in this.options.message ) { // use custom os message
477
+ message = this.options.message[ath.OS];
478
+ } else if ( this.options.message in ath.intl ) { // you can force the locale
479
+ message = ath.intl[this.options.message][ath.OS];
480
+ } else if ( this.options.message !== '' ) { // use a custom message
481
+ message = this.options.message;
482
+ } else if ( ath.OS in ath.intl[ath.language] ) { // otherwise we use our message
483
+ message = ath.intl[ath.language][ath.OS];
484
+ }
485
+
486
+ // add the action icon
487
+ message = '<p>' + message.replace(/%icon(?:\[([^\]]+)\])?/gi, function(matches, group1) {
488
+ return '<span class="ath-action-icon">' + (!!group1 ? group1 : 'icon') + '</span>';
489
+ }); + '</p>';
490
+
491
+ // create the message container
492
+ this.viewport = document.createElement('div');
493
+ this.viewport.className = 'ath-viewport';
494
+ if ( this.options.modal ) {
495
+ this.viewport.className += ' ath-modal';
496
+ }
497
+ if ( this.options.mandatory ) {
498
+ this.viewport.className += ' ath-mandatory';
499
+ }
500
+ this.viewport.style.position = 'absolute';
501
+
502
+ // create the actual message element
503
+ this.element = document.createElement('div');
504
+ this.element.className = 'ath-container ath-' + ath.OS + ' ath-' + ath.OS + (ath.OSVersion + '').substr(0,1) + ' ath-' + (ath.isTablet ? 'tablet' : 'phone');
505
+ this.element.style.cssText = '-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0s;-webkit-transition-timing-function:ease-out;transition-property:transform,opacity;transition-duration:0s;transition-timing-function:ease-out;';
506
+ this.element.style.webkitTransform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
507
+ this.element.style.transform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
508
+
509
+ // add the application icon
510
+ if ( this.options.icon && this.applicationIcon ) {
511
+ this.element.className += ' ath-icon';
512
+ this.img = document.createElement('img');
513
+ this.img.className = 'ath-application-icon';
514
+ this.img.addEventListener('load', this, false);
515
+ this.img.addEventListener('error', this, false);
516
+
517
+ this.img.src = this.applicationIcon.href;
518
+ this.element.appendChild(this.img);
519
+ }
520
+
521
+ this.element.innerHTML += message;
522
+
523
+ // we are not ready to show, place the message out of sight
524
+ this.viewport.style.left = '-99999em';
525
+
526
+ // attach all elements to the DOM
527
+ this.viewport.appendChild(this.element);
528
+ this.container.appendChild(this.viewport);
529
+
530
+ // if we don't have to wait for an image to load, show the message right away
531
+ if ( this.img ) {
532
+ this.doLog("Add to homescreen: not displaying callout because waiting for img to load");
533
+ } else {
534
+ this._delayedShow();
535
+ }
536
+ },
537
+
538
+ _delayedShow: function (e) {
539
+ setTimeout(this._show.bind(this), this.options.startDelay * 1000 + 500);
540
+ },
541
+
542
+ _show: function () {
543
+ var that = this;
544
+
545
+ // update the viewport size and orientation
546
+ this.updateViewport();
547
+
548
+ // reposition/resize the message on orientation change
549
+ window.addEventListener('resize', this, false);
550
+ window.addEventListener('scroll', this, false);
551
+ window.addEventListener('orientationchange', this, false);
552
+
553
+ if ( this.options.modal ) {
554
+ // lock any other interaction
555
+ document.addEventListener('touchmove', this, true);
556
+ }
557
+
558
+ // Enable closing after 1 second
559
+ if ( !this.options.mandatory ) {
560
+ setTimeout(function () {
561
+ that.element.addEventListener('click', that, true);
562
+ }, 1000);
563
+ }
564
+
565
+ // kick the animation
566
+ setTimeout(function () {
567
+ that.element.style.webkitTransitionDuration = '1.2s';
568
+ that.element.style.transitionDuration = '1.2s';
569
+ that.element.style.webkitTransform = 'translate3d(0,0,0)';
570
+ that.element.style.transform = 'translate3d(0,0,0)';
571
+ }, 0);
572
+
573
+ // set the destroy timer
574
+ if ( this.options.lifespan ) {
575
+ this.removeTimer = setTimeout(this.remove.bind(this), this.options.lifespan * 1000);
576
+ }
577
+
578
+ // fire the custom onShow event
579
+ if ( this.options.onShow ) {
580
+ this.options.onShow.call(this);
581
+ }
582
+ },
583
+
584
+ remove: function () {
585
+ clearTimeout(this.removeTimer);
586
+
587
+ // clear up the event listeners
588
+ if ( this.img ) {
589
+ this.img.removeEventListener('load', this, false);
590
+ this.img.removeEventListener('error', this, false);
591
+ }
592
+
593
+ window.removeEventListener('resize', this, false);
594
+ window.removeEventListener('scroll', this, false);
595
+ window.removeEventListener('orientationchange', this, false);
596
+ document.removeEventListener('touchmove', this, true);
597
+ this.element.removeEventListener('click', this, true);
598
+
599
+ // remove the message element on transition end
600
+ this.element.addEventListener('transitionend', this, false);
601
+ this.element.addEventListener('webkitTransitionEnd', this, false);
602
+ this.element.addEventListener('MSTransitionEnd', this, false);
603
+
604
+ // start the fade out animation
605
+ this.element.style.webkitTransitionDuration = '0.3s';
606
+ this.element.style.opacity = '0';
607
+ },
608
+
609
+ _removeElements: function () {
610
+ this.element.removeEventListener('transitionend', this, false);
611
+ this.element.removeEventListener('webkitTransitionEnd', this, false);
612
+ this.element.removeEventListener('MSTransitionEnd', this, false);
613
+
614
+ // remove the message from the DOM
615
+ this.container.removeChild(this.viewport);
616
+
617
+ this.shown = false;
618
+
619
+ // fire the custom onRemove event
620
+ if ( this.options.onRemove ) {
621
+ this.options.onRemove.call(this);
622
+ }
623
+ },
624
+
625
+ updateViewport: function () {
626
+ if ( !this.shown ) {
627
+ return;
628
+ }
629
+
630
+ this.viewport.style.width = window.innerWidth + 'px';
631
+ this.viewport.style.height = window.innerHeight + 'px';
632
+ this.viewport.style.left = window.scrollX + 'px';
633
+ this.viewport.style.top = window.scrollY + 'px';
634
+
635
+ var clientWidth = document.documentElement.clientWidth;
636
+
637
+ this.orientation = clientWidth > document.documentElement.clientHeight ? 'landscape' : 'portrait';
638
+
639
+ var screenWidth = ath.OS == 'ios' ? this.orientation == 'portrait' ? screen.width : screen.height : screen.width;
640
+ this.scale = screen.width > clientWidth ? 1 : screenWidth / window.innerWidth;
641
+
642
+ this.element.style.fontSize = this.options.fontSize / this.scale + 'px';
643
+ },
644
+
645
+ resize: function () {
646
+ clearTimeout(this.resizeTimer);
647
+ this.resizeTimer = setTimeout(this.updateViewport.bind(this), 100);
648
+ },
649
+
650
+ updateSession: function () {
651
+ if ( ath.hasLocalStorage === false ) {
652
+ return;
653
+ }
654
+
655
+ if (localStorage) {
656
+ localStorage.setItem(this.options.appID, JSON.stringify(this.session));
657
+ }
658
+ },
659
+
660
+ clearSession: function () {
661
+ this.session = _defaultSession;
662
+ this.updateSession();
663
+ },
664
+
665
+ getItem: function(item) {
666
+ try {
667
+ if (!localStorage) {
668
+ throw new Error('localStorage is not defined');
669
+ }
670
+
671
+ return localStorage.getItem(item);
672
+ } catch(e) {
673
+ // Preventing exception for some browsers when fetching localStorage key
674
+ ath.hasLocalStorage = false;
675
+ }
676
+ },
677
+
678
+ optOut: function () {
679
+ this.session.optedout = true;
680
+ this.updateSession();
681
+ },
682
+
683
+ optIn: function () {
684
+ this.session.optedout = false;
685
+ this.updateSession();
686
+ },
687
+
688
+ clearDisplayCount: function () {
689
+ this.session.displayCount = 0;
690
+ this.updateSession();
691
+ },
692
+
693
+ _preventDefault: function (e) {
694
+ e.preventDefault();
695
+ e.stopPropagation();
696
+ }
697
+ };
698
+
699
+ // utility
700
+ function _extend (target, obj) {
701
+ for ( var i in obj ) {
702
+ target[i] = obj[i];
703
+ }
704
+
705
+ return target;
706
+ }
707
+
708
+ function _removeToken () {
709
+ if ( document.location.hash == '#ath' ) {
710
+ history.replaceState('', window.document.title, document.location.href.split('#')[0]);
711
+ }
712
+
713
+ if ( _reSmartURL.test(document.location.href) ) {
714
+ history.replaceState('', window.document.title, document.location.href.replace(_reSmartURL, '$1'));
715
+ }
716
+
717
+ if ( _reQueryString.test(document.location.search) ) {
718
+ history.replaceState('', window.document.title, document.location.href.replace(_reQueryString, '$2'));
719
+ }
720
+ }
721
+
722
+ // expose to the world
723
+ window.addToHomescreen = ath;
724
+
725
+ })(window, document);
lib/gh-fork-ribbon.css ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * "Fork me on GitHub" CSS ribbon v0.1.1 | MIT License
3
+ * https://github.com/simonwhitaker/github-fork-ribbon-css
4
+ */
5
+
6
+ /* Left will inherit from right (so we don't need to duplicate code) */
7
+ .github-fork-ribbon {
8
+ /* The right and left classes determine the side we attach our banner to */
9
+ position: absolute;
10
+
11
+ /* Add a bit of padding to give some substance outside the "stitching" */
12
+ padding: 2px 0;
13
+
14
+ /* Set the base colour */
15
+ background-color: #a00;
16
+
17
+ /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */
18
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));
19
+ background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
20
+ background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
21
+ background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
22
+ background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
23
+ background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
24
+
25
+ /* Add a drop shadow */
26
+ -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
27
+ -moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
28
+ box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
29
+
30
+ /* Set the font */
31
+ font: 700 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
32
+
33
+ z-index: 9999;
34
+ pointer-events: auto;
35
+ }
36
+
37
+ .github-fork-ribbon a,
38
+ .github-fork-ribbon a:hover {
39
+ /* Set the text properties */
40
+ color: #fff;
41
+ text-decoration: none;
42
+ text-shadow: 0 -1px rgba(0, 0, 0, 0.5);
43
+ text-align: center;
44
+
45
+ /* Set the geometry. If you fiddle with these you'll also need
46
+ to tweak the top and right values in .github-fork-ribbon. */
47
+ width: 200px;
48
+ line-height: 20px;
49
+
50
+ /* Set the layout properties */
51
+ display: inline-block;
52
+ padding: 2px 0;
53
+
54
+ /* Add "stitching" effect */
55
+ border-width: 1px 0;
56
+ border-style: dotted;
57
+ border-color: #fff;
58
+ border-color: rgba(255, 255, 255, 0.7);
59
+ }
60
+
61
+ .github-fork-ribbon-wrapper {
62
+ width: 150px;
63
+ height: 150px;
64
+ position: absolute;
65
+ overflow: hidden;
66
+ top: 0;
67
+ z-index: 9999;
68
+ pointer-events: none;
69
+ }
70
+
71
+ .github-fork-ribbon-wrapper.fixed {
72
+ position: fixed;
73
+ }
74
+
75
+ .github-fork-ribbon-wrapper.left {
76
+ left: 0;
77
+ }
78
+
79
+ .github-fork-ribbon-wrapper.right {
80
+ right: 0;
81
+ }
82
+
83
+ .github-fork-ribbon-wrapper.left-bottom {
84
+ position: fixed;
85
+ top: inherit;
86
+ bottom: 0;
87
+ left: 0;
88
+ }
89
+
90
+ .github-fork-ribbon-wrapper.right-bottom {
91
+ position: fixed;
92
+ top: inherit;
93
+ bottom: 0;
94
+ right: 0;
95
+ }
96
+
97
+ .github-fork-ribbon-wrapper.right .github-fork-ribbon {
98
+ top: 42px;
99
+ right: -43px;
100
+
101
+ -webkit-transform: rotate(45deg);
102
+ -moz-transform: rotate(45deg);
103
+ -ms-transform: rotate(45deg);
104
+ -o-transform: rotate(45deg);
105
+ transform: rotate(45deg);
106
+ }
107
+
108
+ .github-fork-ribbon-wrapper.left .github-fork-ribbon {
109
+ top: 42px;
110
+ left: -43px;
111
+
112
+ -webkit-transform: rotate(-45deg);
113
+ -moz-transform: rotate(-45deg);
114
+ -ms-transform: rotate(-45deg);
115
+ -o-transform: rotate(-45deg);
116
+ transform: rotate(-45deg);
117
+ }
118
+
119
+
120
+ .github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon {
121
+ top: 80px;
122
+ left: -43px;
123
+
124
+ -webkit-transform: rotate(45deg);
125
+ -moz-transform: rotate(45deg);
126
+ -ms-transform: rotate(45deg);
127
+ -o-transform: rotate(45deg);
128
+ transform: rotate(45deg);
129
+ }
130
+
131
+ .github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon {
132
+ top: 80px;
133
+ right: -43px;
134
+
135
+ -webkit-transform: rotate(-45deg);
136
+ -moz-transform: rotate(-45deg);
137
+ -ms-transform: rotate(-45deg);
138
+ -o-transform: rotate(-45deg);
139
+ transform: rotate(-45deg);
140
+ }
lib/jszip.js ADDED
The diff for this file is too large to render. See raw diff
 
lib/lz-string.js ADDED
@@ -0,0 +1,665 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright (c) 2013 Pieroxy <[email protected]>
2
+ // This work is free. You can redistribute it and/or modify it
3
+ // under the terms of the WTFPL, Version 2
4
+ // For more information see LICENSE.txt or http://www.wtfpl.net/
5
+ //
6
+ // For more information, the home page:
7
+ // http://pieroxy.net/blog/pages/lz-string/testing.html
8
+ //
9
+ // LZ-based compression algorithm, version 1.3.3
10
+ var LZString = {
11
+
12
+
13
+ // private property
14
+ _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
15
+ _f : String.fromCharCode,
16
+
17
+ compressToBase64 : function (input) {
18
+ if (input == null) return "";
19
+ var output = "";
20
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
21
+ var i = 0;
22
+
23
+ input = LZString.compress(input);
24
+
25
+ while (i < input.length*2) {
26
+
27
+ if (i%2==0) {
28
+ chr1 = input.charCodeAt(i/2) >> 8;
29
+ chr2 = input.charCodeAt(i/2) & 255;
30
+ if (i/2+1 < input.length)
31
+ chr3 = input.charCodeAt(i/2+1) >> 8;
32
+ else
33
+ chr3 = NaN;
34
+ } else {
35
+ chr1 = input.charCodeAt((i-1)/2) & 255;
36
+ if ((i+1)/2 < input.length) {
37
+ chr2 = input.charCodeAt((i+1)/2) >> 8;
38
+ chr3 = input.charCodeAt((i+1)/2) & 255;
39
+ } else
40
+ chr2=chr3=NaN;
41
+ }
42
+ i+=3;
43
+
44
+ enc1 = chr1 >> 2;
45
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
46
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
47
+ enc4 = chr3 & 63;
48
+
49
+ if (isNaN(chr2)) {
50
+ enc3 = enc4 = 64;
51
+ } else if (isNaN(chr3)) {
52
+ enc4 = 64;
53
+ }
54
+
55
+ output = output +
56
+ LZString._keyStr.charAt(enc1) + LZString._keyStr.charAt(enc2) +
57
+ LZString._keyStr.charAt(enc3) + LZString._keyStr.charAt(enc4);
58
+
59
+ }
60
+
61
+ return output;
62
+ },
63
+
64
+ decompressFromBase64 : function (input) {
65
+ if (input == null) return "";
66
+ var output = "",
67
+ ol = 0,
68
+ output_,
69
+ chr1, chr2, chr3,
70
+ enc1, enc2, enc3, enc4,
71
+ i = 0, f=LZString._f;
72
+
73
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
74
+
75
+ while (i < input.length) {
76
+
77
+ enc1 = LZString._keyStr.indexOf(input.charAt(i++));
78
+ enc2 = LZString._keyStr.indexOf(input.charAt(i++));
79
+ enc3 = LZString._keyStr.indexOf(input.charAt(i++));
80
+ enc4 = LZString._keyStr.indexOf(input.charAt(i++));
81
+
82
+ chr1 = (enc1 << 2) | (enc2 >> 4);
83
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
84
+ chr3 = ((enc3 & 3) << 6) | enc4;
85
+
86
+ if (ol%2==0) {
87
+ output_ = chr1 << 8;
88
+
89
+ if (enc3 != 64) {
90
+ output += f(output_ | chr2);
91
+ }
92
+ if (enc4 != 64) {
93
+ output_ = chr3 << 8;
94
+ }
95
+ } else {
96
+ output = output + f(output_ | chr1);
97
+
98
+ if (enc3 != 64) {
99
+ output_ = chr2 << 8;
100
+ }
101
+ if (enc4 != 64) {
102
+ output += f(output_ | chr3);
103
+ }
104
+ }
105
+ ol+=3;
106
+ }
107
+
108
+ return LZString.decompress(output);
109
+
110
+ },
111
+
112
+ compressToUTF16 : function (input) {
113
+ if (input == null) return "";
114
+ var output = "",
115
+ i,c,
116
+ current,
117
+ status = 0,
118
+ f = LZString._f;
119
+
120
+ input = LZString.compress(input);
121
+
122
+ for (i=0 ; i<input.length ; i++) {
123
+ c = input.charCodeAt(i);
124
+ switch (status++) {
125
+ case 0:
126
+ output += f((c >> 1)+32);
127
+ current = (c & 1) << 14;
128
+ break;
129
+ case 1:
130
+ output += f((current + (c >> 2))+32);
131
+ current = (c & 3) << 13;
132
+ break;
133
+ case 2:
134
+ output += f((current + (c >> 3))+32);
135
+ current = (c & 7) << 12;
136
+ break;
137
+ case 3:
138
+ output += f((current + (c >> 4))+32);
139
+ current = (c & 15) << 11;
140
+ break;
141
+ case 4:
142
+ output += f((current + (c >> 5))+32);
143
+ current = (c & 31) << 10;
144
+ break;
145
+ case 5:
146
+ output += f((current + (c >> 6))+32);
147
+ current = (c & 63) << 9;
148
+ break;
149
+ case 6:
150
+ output += f((current + (c >> 7))+32);
151
+ current = (c & 127) << 8;
152
+ break;
153
+ case 7:
154
+ output += f((current + (c >> 8))+32);
155
+ current = (c & 255) << 7;
156
+ break;
157
+ case 8:
158
+ output += f((current + (c >> 9))+32);
159
+ current = (c & 511) << 6;
160
+ break;
161
+ case 9:
162
+ output += f((current + (c >> 10))+32);
163
+ current = (c & 1023) << 5;
164
+ break;
165
+ case 10:
166
+ output += f((current + (c >> 11))+32);
167
+ current = (c & 2047) << 4;
168
+ break;
169
+ case 11:
170
+ output += f((current + (c >> 12))+32);
171
+ current = (c & 4095) << 3;
172
+ break;
173
+ case 12:
174
+ output += f((current + (c >> 13))+32);
175
+ current = (c & 8191) << 2;
176
+ break;
177
+ case 13:
178
+ output += f((current + (c >> 14))+32);
179
+ current = (c & 16383) << 1;
180
+ break;
181
+ case 14:
182
+ output += f((current + (c >> 15))+32, (c & 32767)+32);
183
+ status = 0;
184
+ break;
185
+ }
186
+ }
187
+
188
+ return output + f(current + 32);
189
+ },
190
+
191
+
192
+ decompressFromUTF16 : function (input) {
193
+ if (input == null) return "";
194
+ var output = "",
195
+ current,c,
196
+ status=0,
197
+ i = 0,
198
+ f = LZString._f;
199
+
200
+ while (i < input.length) {
201
+ c = input.charCodeAt(i) - 32;
202
+
203
+ switch (status++) {
204
+ case 0:
205
+ current = c << 1;
206
+ break;
207
+ case 1:
208
+ output += f(current | (c >> 14));
209
+ current = (c&16383) << 2;
210
+ break;
211
+ case 2:
212
+ output += f(current | (c >> 13));
213
+ current = (c&8191) << 3;
214
+ break;
215
+ case 3:
216
+ output += f(current | (c >> 12));
217
+ current = (c&4095) << 4;
218
+ break;
219
+ case 4:
220
+ output += f(current | (c >> 11));
221
+ current = (c&2047) << 5;
222
+ break;
223
+ case 5:
224
+ output += f(current | (c >> 10));
225
+ current = (c&1023) << 6;
226
+ break;
227
+ case 6:
228
+ output += f(current | (c >> 9));
229
+ current = (c&511) << 7;
230
+ break;
231
+ case 7:
232
+ output += f(current | (c >> 8));
233
+ current = (c&255) << 8;
234
+ break;
235
+ case 8:
236
+ output += f(current | (c >> 7));
237
+ current = (c&127) << 9;
238
+ break;
239
+ case 9:
240
+ output += f(current | (c >> 6));
241
+ current = (c&63) << 10;
242
+ break;
243
+ case 10:
244
+ output += f(current | (c >> 5));
245
+ current = (c&31) << 11;
246
+ break;
247
+ case 11:
248
+ output += f(current | (c >> 4));
249
+ current = (c&15) << 12;
250
+ break;
251
+ case 12:
252
+ output += f(current | (c >> 3));
253
+ current = (c&7) << 13;
254
+ break;
255
+ case 13:
256
+ output += f(current | (c >> 2));
257
+ current = (c&3) << 14;
258
+ break;
259
+ case 14:
260
+ output += f(current | (c >> 1));
261
+ current = (c&1) << 15;
262
+ break;
263
+ case 15:
264
+ output += f(current | c);
265
+ status=0;
266
+ break;
267
+ }
268
+
269
+
270
+ i++;
271
+ }
272
+
273
+ return LZString.decompress(output);
274
+ //return output;
275
+
276
+ },
277
+
278
+
279
+
280
+ compress: function (uncompressed) {
281
+ if (uncompressed == null) return "";
282
+ var i, value,
283
+ context_dictionary= {},
284
+ context_dictionaryToCreate= {},
285
+ context_c="",
286
+ context_wc="",
287
+ context_w="",
288
+ context_enlargeIn= 2, // Compensate for the first entry which should not count
289
+ context_dictSize= 3,
290
+ context_numBits= 2,
291
+ context_data_string="",
292
+ context_data_val=0,
293
+ context_data_position=0,
294
+ ii,
295
+ f=LZString._f;
296
+
297
+ for (ii = 0; ii < uncompressed.length; ii += 1) {
298
+ context_c = uncompressed.charAt(ii);
299
+ if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) {
300
+ context_dictionary[context_c] = context_dictSize++;
301
+ context_dictionaryToCreate[context_c] = true;
302
+ }
303
+
304
+ context_wc = context_w + context_c;
305
+ if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) {
306
+ context_w = context_wc;
307
+ } else {
308
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
309
+ if (context_w.charCodeAt(0)<256) {
310
+ for (i=0 ; i<context_numBits ; i++) {
311
+ context_data_val = (context_data_val << 1);
312
+ if (context_data_position == 15) {
313
+ context_data_position = 0;
314
+ context_data_string += f(context_data_val);
315
+ context_data_val = 0;
316
+ } else {
317
+ context_data_position++;
318
+ }
319
+ }
320
+ value = context_w.charCodeAt(0);
321
+ for (i=0 ; i<8 ; i++) {
322
+ context_data_val = (context_data_val << 1) | (value&1);
323
+ if (context_data_position == 15) {
324
+ context_data_position = 0;
325
+ context_data_string += f(context_data_val);
326
+ context_data_val = 0;
327
+ } else {
328
+ context_data_position++;
329
+ }
330
+ value = value >> 1;
331
+ }
332
+ } else {
333
+ value = 1;
334
+ for (i=0 ; i<context_numBits ; i++) {
335
+ context_data_val = (context_data_val << 1) | value;
336
+ if (context_data_position == 15) {
337
+ context_data_position = 0;
338
+ context_data_string += f(context_data_val);
339
+ context_data_val = 0;
340
+ } else {
341
+ context_data_position++;
342
+ }
343
+ value = 0;
344
+ }
345
+ value = context_w.charCodeAt(0);
346
+ for (i=0 ; i<16 ; i++) {
347
+ context_data_val = (context_data_val << 1) | (value&1);
348
+ if (context_data_position == 15) {
349
+ context_data_position = 0;
350
+ context_data_string += f(context_data_val);
351
+ context_data_val = 0;
352
+ } else {
353
+ context_data_position++;
354
+ }
355
+ value = value >> 1;
356
+ }
357
+ }
358
+ context_enlargeIn--;
359
+ if (context_enlargeIn == 0) {
360
+ context_enlargeIn = Math.pow(2, context_numBits);
361
+ context_numBits++;
362
+ }
363
+ delete context_dictionaryToCreate[context_w];
364
+ } else {
365
+ value = context_dictionary[context_w];
366
+ for (i=0 ; i<context_numBits ; i++) {
367
+ context_data_val = (context_data_val << 1) | (value&1);
368
+ if (context_data_position == 15) {
369
+ context_data_position = 0;
370
+ context_data_string += f(context_data_val);
371
+ context_data_val = 0;
372
+ } else {
373
+ context_data_position++;
374
+ }
375
+ value = value >> 1;
376
+ }
377
+
378
+
379
+ }
380
+ context_enlargeIn--;
381
+ if (context_enlargeIn == 0) {
382
+ context_enlargeIn = Math.pow(2, context_numBits);
383
+ context_numBits++;
384
+ }
385
+ // Add wc to the dictionary.
386
+ context_dictionary[context_wc] = context_dictSize++;
387
+ context_w = String(context_c);
388
+ }
389
+ }
390
+
391
+ // Output the code for w.
392
+ if (context_w !== "") {
393
+ if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
394
+ if (context_w.charCodeAt(0)<256) {
395
+ for (i=0 ; i<context_numBits ; i++) {
396
+ context_data_val = (context_data_val << 1);
397
+ if (context_data_position == 15) {
398
+ context_data_position = 0;
399
+ context_data_string += f(context_data_val);
400
+ context_data_val = 0;
401
+ } else {
402
+ context_data_position++;
403
+ }
404
+ }
405
+ value = context_w.charCodeAt(0);
406
+ for (i=0 ; i<8 ; i++) {
407
+ context_data_val = (context_data_val << 1) | (value&1);
408
+ if (context_data_position == 15) {
409
+ context_data_position = 0;
410
+ context_data_string += f(context_data_val);
411
+ context_data_val = 0;
412
+ } else {
413
+ context_data_position++;
414
+ }
415
+ value = value >> 1;
416
+ }
417
+ } else {
418
+ value = 1;
419
+ for (i=0 ; i<context_numBits ; i++) {
420
+ context_data_val = (context_data_val << 1) | value;
421
+ if (context_data_position == 15) {
422
+ context_data_position = 0;
423
+ context_data_string += f(context_data_val);
424
+ context_data_val = 0;
425
+ } else {
426
+ context_data_position++;
427
+ }
428
+ value = 0;
429
+ }
430
+ value = context_w.charCodeAt(0);
431
+ for (i=0 ; i<16 ; i++) {
432
+ context_data_val = (context_data_val << 1) | (value&1);
433
+ if (context_data_position == 15) {
434
+ context_data_position = 0;
435
+ context_data_string += f(context_data_val);
436
+ context_data_val = 0;
437
+ } else {
438
+ context_data_position++;
439
+ }
440
+ value = value >> 1;
441
+ }
442
+ }
443
+ context_enlargeIn--;
444
+ if (context_enlargeIn == 0) {
445
+ context_enlargeIn = Math.pow(2, context_numBits);
446
+ context_numBits++;
447
+ }
448
+ delete context_dictionaryToCreate[context_w];
449
+ } else {
450
+ value = context_dictionary[context_w];
451
+ for (i=0 ; i<context_numBits ; i++) {
452
+ context_data_val = (context_data_val << 1) | (value&1);
453
+ if (context_data_position == 15) {
454
+ context_data_position = 0;
455
+ context_data_string += f(context_data_val);
456
+ context_data_val = 0;
457
+ } else {
458
+ context_data_position++;
459
+ }
460
+ value = value >> 1;
461
+ }
462
+
463
+
464
+ }
465
+ context_enlargeIn--;
466
+ if (context_enlargeIn == 0) {
467
+ context_enlargeIn = Math.pow(2, context_numBits);
468
+ context_numBits++;
469
+ }
470
+ }
471
+
472
+ // Mark the end of the stream
473
+ value = 2;
474
+ for (i=0 ; i<context_numBits ; i++) {
475
+ context_data_val = (context_data_val << 1) | (value&1);
476
+ if (context_data_position == 15) {
477
+ context_data_position = 0;
478
+ context_data_string += f(context_data_val);
479
+ context_data_val = 0;
480
+ } else {
481
+ context_data_position++;
482
+ }
483
+ value = value >> 1;
484
+ }
485
+
486
+ // Flush the last char
487
+ while (true) {
488
+ context_data_val = (context_data_val << 1);
489
+ if (context_data_position == 15) {
490
+ context_data_string += f(context_data_val);
491
+ break;
492
+ }
493
+ else context_data_position++;
494
+ }
495
+ return context_data_string;
496
+ },
497
+
498
+ decompress: function (compressed) {
499
+ if (compressed == null) return "";
500
+ if (compressed == "") return null;
501
+ var dictionary = [],
502
+ next,
503
+ enlargeIn = 4,
504
+ dictSize = 4,
505
+ numBits = 3,
506
+ entry = "",
507
+ result = "",
508
+ i,
509
+ w,
510
+ bits, resb, maxpower, power,
511
+ c,
512
+ f = LZString._f,
513
+ data = {string:compressed, val:compressed.charCodeAt(0), position:32768, index:1};
514
+
515
+ for (i = 0; i < 3; i += 1) {
516
+ dictionary[i] = i;
517
+ }
518
+
519
+ bits = 0;
520
+ maxpower = Math.pow(2,2);
521
+ power=1;
522
+ while (power!=maxpower) {
523
+ resb = data.val & data.position;
524
+ data.position >>= 1;
525
+ if (data.position == 0) {
526
+ data.position = 32768;
527
+ data.val = data.string.charCodeAt(data.index++);
528
+ }
529
+ bits |= (resb>0 ? 1 : 0) * power;
530
+ power <<= 1;
531
+ }
532
+
533
+ switch (next = bits) {
534
+ case 0:
535
+ bits = 0;
536
+ maxpower = Math.pow(2,8);
537
+ power=1;
538
+ while (power!=maxpower) {
539
+ resb = data.val & data.position;
540
+ data.position >>= 1;
541
+ if (data.position == 0) {
542
+ data.position = 32768;
543
+ data.val = data.string.charCodeAt(data.index++);
544
+ }
545
+ bits |= (resb>0 ? 1 : 0) * power;
546
+ power <<= 1;
547
+ }
548
+ c = f(bits);
549
+ break;
550
+ case 1:
551
+ bits = 0;
552
+ maxpower = Math.pow(2,16);
553
+ power=1;
554
+ while (power!=maxpower) {
555
+ resb = data.val & data.position;
556
+ data.position >>= 1;
557
+ if (data.position == 0) {
558
+ data.position = 32768;
559
+ data.val = data.string.charCodeAt(data.index++);
560
+ }
561
+ bits |= (resb>0 ? 1 : 0) * power;
562
+ power <<= 1;
563
+ }
564
+ c = f(bits);
565
+ break;
566
+ case 2:
567
+ return "";
568
+ }
569
+ dictionary[3] = c;
570
+ w = result = c;
571
+ while (true) {
572
+ if (data.index > data.string.length) {
573
+ return "";
574
+ }
575
+
576
+ bits = 0;
577
+ maxpower = Math.pow(2,numBits);
578
+ power=1;
579
+ while (power!=maxpower) {
580
+ resb = data.val & data.position;
581
+ data.position >>= 1;
582
+ if (data.position == 0) {
583
+ data.position = 32768;
584
+ data.val = data.string.charCodeAt(data.index++);
585
+ }
586
+ bits |= (resb>0 ? 1 : 0) * power;
587
+ power <<= 1;
588
+ }
589
+
590
+ switch (c = bits) {
591
+ case 0:
592
+ bits = 0;
593
+ maxpower = Math.pow(2,8);
594
+ power=1;
595
+ while (power!=maxpower) {
596
+ resb = data.val & data.position;
597
+ data.position >>= 1;
598
+ if (data.position == 0) {
599
+ data.position = 32768;
600
+ data.val = data.string.charCodeAt(data.index++);
601
+ }
602
+ bits |= (resb>0 ? 1 : 0) * power;
603
+ power <<= 1;
604
+ }
605
+
606
+ dictionary[dictSize++] = f(bits);
607
+ c = dictSize-1;
608
+ enlargeIn--;
609
+ break;
610
+ case 1:
611
+ bits = 0;
612
+ maxpower = Math.pow(2,16);
613
+ power=1;
614
+ while (power!=maxpower) {
615
+ resb = data.val & data.position;
616
+ data.position >>= 1;
617
+ if (data.position == 0) {
618
+ data.position = 32768;
619
+ data.val = data.string.charCodeAt(data.index++);
620
+ }
621
+ bits |= (resb>0 ? 1 : 0) * power;
622
+ power <<= 1;
623
+ }
624
+ dictionary[dictSize++] = f(bits);
625
+ c = dictSize-1;
626
+ enlargeIn--;
627
+ break;
628
+ case 2:
629
+ return result;
630
+ }
631
+
632
+ if (enlargeIn == 0) {
633
+ enlargeIn = Math.pow(2, numBits);
634
+ numBits++;
635
+ }
636
+
637
+ if (dictionary[c]) {
638
+ entry = dictionary[c];
639
+ } else {
640
+ if (c === dictSize) {
641
+ entry = w + w.charAt(0);
642
+ } else {
643
+ return null;
644
+ }
645
+ }
646
+ result += entry;
647
+
648
+ // Add w+entry[0] to the dictionary.
649
+ dictionary[dictSize++] = w + entry.charAt(0);
650
+ enlargeIn--;
651
+
652
+ w = entry;
653
+
654
+ if (enlargeIn == 0) {
655
+ enlargeIn = Math.pow(2, numBits);
656
+ numBits++;
657
+ }
658
+
659
+ }
660
+ }
661
+ };
662
+
663
+ if( typeof module !== 'undefined' && module != null ) {
664
+ module.exports = LZString
665
+ }
lib/sha1.js ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * [js-sha1]{@link https://github.com/emn178/js-sha1}
3
+ *
4
+ * @version 0.6.0
5
+ * @author Chen, Yi-Cyuan [[email protected]]
6
+ * @copyright Chen, Yi-Cyuan 2014-2017
7
+ * @license MIT
8
+ */
9
+ /*jslint bitwise: true */
10
+ (function() {
11
+ 'use strict';
12
+
13
+ var root = typeof window === 'object' ? window : {};
14
+ var NODE_JS = !root.JS_SHA1_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
15
+ if (NODE_JS) {
16
+ root = global;
17
+ }
18
+ var COMMON_JS = !root.JS_SHA1_NO_COMMON_JS && typeof module === 'object' && module.exports;
19
+ var AMD = typeof define === 'function' && define.amd;
20
+ var HEX_CHARS = '0123456789abcdef'.split('');
21
+ var EXTRA = [-2147483648, 8388608, 32768, 128];
22
+ var SHIFT = [24, 16, 8, 0];
23
+ var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer'];
24
+
25
+ var blocks = [];
26
+
27
+ var createOutputMethod = function (outputType) {
28
+ return function (message) {
29
+ return new Sha1(true).update(message)[outputType]();
30
+ };
31
+ };
32
+
33
+ var createMethod = function () {
34
+ var method = createOutputMethod('hex');
35
+ if (NODE_JS) {
36
+ method = nodeWrap(method);
37
+ }
38
+ method.create = function () {
39
+ return new Sha1();
40
+ };
41
+ method.update = function (message) {
42
+ return method.create().update(message);
43
+ };
44
+ for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
45
+ var type = OUTPUT_TYPES[i];
46
+ method[type] = createOutputMethod(type);
47
+ }
48
+ return method;
49
+ };
50
+
51
+ var nodeWrap = function (method) {
52
+ var crypto = eval("require('crypto')");
53
+ var Buffer = eval("require('buffer').Buffer");
54
+ var nodeMethod = function (message) {
55
+ if (typeof message === 'string') {
56
+ return crypto.createHash('sha1').update(message, 'utf8').digest('hex');
57
+ } else if (message.constructor === ArrayBuffer) {
58
+ message = new Uint8Array(message);
59
+ } else if (message.length === undefined) {
60
+ return method(message);
61
+ }
62
+ return crypto.createHash('sha1').update(new Buffer(message)).digest('hex');
63
+ };
64
+ return nodeMethod;
65
+ };
66
+
67
+ function Sha1(sharedMemory) {
68
+ if (sharedMemory) {
69
+ blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
70
+ blocks[4] = blocks[5] = blocks[6] = blocks[7] =
71
+ blocks[8] = blocks[9] = blocks[10] = blocks[11] =
72
+ blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
73
+ this.blocks = blocks;
74
+ } else {
75
+ this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
76
+ }
77
+
78
+ this.h0 = 0x67452301;
79
+ this.h1 = 0xEFCDAB89;
80
+ this.h2 = 0x98BADCFE;
81
+ this.h3 = 0x10325476;
82
+ this.h4 = 0xC3D2E1F0;
83
+
84
+ this.block = this.start = this.bytes = this.hBytes = 0;
85
+ this.finalized = this.hashed = false;
86
+ this.first = true;
87
+ }
88
+
89
+ Sha1.prototype.update = function (message) {
90
+ if (this.finalized) {
91
+ return;
92
+ }
93
+ var notString = typeof(message) !== 'string';
94
+ if (notString && message.constructor === root.ArrayBuffer) {
95
+ message = new Uint8Array(message);
96
+ }
97
+ var code, index = 0, i, length = message.length || 0, blocks = this.blocks;
98
+
99
+ while (index < length) {
100
+ if (this.hashed) {
101
+ this.hashed = false;
102
+ blocks[0] = this.block;
103
+ blocks[16] = blocks[1] = blocks[2] = blocks[3] =
104
+ blocks[4] = blocks[5] = blocks[6] = blocks[7] =
105
+ blocks[8] = blocks[9] = blocks[10] = blocks[11] =
106
+ blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
107
+ }
108
+
109
+ if(notString) {
110
+ for (i = this.start; index < length && i < 64; ++index) {
111
+ blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
112
+ }
113
+ } else {
114
+ for (i = this.start; index < length && i < 64; ++index) {
115
+ code = message.charCodeAt(index);
116
+ if (code < 0x80) {
117
+ blocks[i >> 2] |= code << SHIFT[i++ & 3];
118
+ } else if (code < 0x800) {
119
+ blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
120
+ blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
121
+ } else if (code < 0xd800 || code >= 0xe000) {
122
+ blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
123
+ blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
124
+ blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
125
+ } else {
126
+ code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
127
+ blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
128
+ blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
129
+ blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
130
+ blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
131
+ }
132
+ }
133
+ }
134
+
135
+ this.lastByteIndex = i;
136
+ this.bytes += i - this.start;
137
+ if (i >= 64) {
138
+ this.block = blocks[16];
139
+ this.start = i - 64;
140
+ this.hash();
141
+ this.hashed = true;
142
+ } else {
143
+ this.start = i;
144
+ }
145
+ }
146
+ if (this.bytes > 4294967295) {
147
+ this.hBytes += this.bytes / 4294967296 << 0;
148
+ this.bytes = this.bytes % 4294967296;
149
+ }
150
+ return this;
151
+ };
152
+
153
+ Sha1.prototype.finalize = function () {
154
+ if (this.finalized) {
155
+ return;
156
+ }
157
+ this.finalized = true;
158
+ var blocks = this.blocks, i = this.lastByteIndex;
159
+ blocks[16] = this.block;
160
+ blocks[i >> 2] |= EXTRA[i & 3];
161
+ this.block = blocks[16];
162
+ if (i >= 56) {
163
+ if (!this.hashed) {
164
+ this.hash();
165
+ }
166
+ blocks[0] = this.block;
167
+ blocks[16] = blocks[1] = blocks[2] = blocks[3] =
168
+ blocks[4] = blocks[5] = blocks[6] = blocks[7] =
169
+ blocks[8] = blocks[9] = blocks[10] = blocks[11] =
170
+ blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
171
+ }
172
+ blocks[14] = this.hBytes << 3 | this.bytes >>> 29;
173
+ blocks[15] = this.bytes << 3;
174
+ this.hash();
175
+ };
176
+
177
+ Sha1.prototype.hash = function () {
178
+ var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4;
179
+ var f, j, t, blocks = this.blocks;
180
+
181
+ for(j = 16; j < 80; ++j) {
182
+ t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
183
+ blocks[j] = (t << 1) | (t >>> 31);
184
+ }
185
+
186
+ for(j = 0; j < 20; j += 5) {
187
+ f = (b & c) | ((~b) & d);
188
+ t = (a << 5) | (a >>> 27);
189
+ e = t + f + e + 1518500249 + blocks[j] << 0;
190
+ b = (b << 30) | (b >>> 2);
191
+
192
+ f = (a & b) | ((~a) & c);
193
+ t = (e << 5) | (e >>> 27);
194
+ d = t + f + d + 1518500249 + blocks[j + 1] << 0;
195
+ a = (a << 30) | (a >>> 2);
196
+
197
+ f = (e & a) | ((~e) & b);
198
+ t = (d << 5) | (d >>> 27);
199
+ c = t + f + c + 1518500249 + blocks[j + 2] << 0;
200
+ e = (e << 30) | (e >>> 2);
201
+
202
+ f = (d & e) | ((~d) & a);
203
+ t = (c << 5) | (c >>> 27);
204
+ b = t + f + b + 1518500249 + blocks[j + 3] << 0;
205
+ d = (d << 30) | (d >>> 2);
206
+
207
+ f = (c & d) | ((~c) & e);
208
+ t = (b << 5) | (b >>> 27);
209
+ a = t + f + a + 1518500249 + blocks[j + 4] << 0;
210
+ c = (c << 30) | (c >>> 2);
211
+ }
212
+
213
+ for(; j < 40; j += 5) {
214
+ f = b ^ c ^ d;
215
+ t = (a << 5) | (a >>> 27);
216
+ e = t + f + e + 1859775393 + blocks[j] << 0;
217
+ b = (b << 30) | (b >>> 2);
218
+
219
+ f = a ^ b ^ c;
220
+ t = (e << 5) | (e >>> 27);
221
+ d = t + f + d + 1859775393 + blocks[j + 1] << 0;
222
+ a = (a << 30) | (a >>> 2);
223
+
224
+ f = e ^ a ^ b;
225
+ t = (d << 5) | (d >>> 27);
226
+ c = t + f + c + 1859775393 + blocks[j + 2] << 0;
227
+ e = (e << 30) | (e >>> 2);
228
+
229
+ f = d ^ e ^ a;
230
+ t = (c << 5) | (c >>> 27);
231
+ b = t + f + b + 1859775393 + blocks[j + 3] << 0;
232
+ d = (d << 30) | (d >>> 2);
233
+
234
+ f = c ^ d ^ e;
235
+ t = (b << 5) | (b >>> 27);
236
+ a = t + f + a + 1859775393 + blocks[j + 4] << 0;
237
+ c = (c << 30) | (c >>> 2);
238
+ }
239
+
240
+ for(; j < 60; j += 5) {
241
+ f = (b & c) | (b & d) | (c & d);
242
+ t = (a << 5) | (a >>> 27);
243
+ e = t + f + e - 1894007588 + blocks[j] << 0;
244
+ b = (b << 30) | (b >>> 2);
245
+
246
+ f = (a & b) | (a & c) | (b & c);
247
+ t = (e << 5) | (e >>> 27);
248
+ d = t + f + d - 1894007588 + blocks[j + 1] << 0;
249
+ a = (a << 30) | (a >>> 2);
250
+
251
+ f = (e & a) | (e & b) | (a & b);
252
+ t = (d << 5) | (d >>> 27);
253
+ c = t + f + c - 1894007588 + blocks[j + 2] << 0;
254
+ e = (e << 30) | (e >>> 2);
255
+
256
+ f = (d & e) | (d & a) | (e & a);
257
+ t = (c << 5) | (c >>> 27);
258
+ b = t + f + b - 1894007588 + blocks[j + 3] << 0;
259
+ d = (d << 30) | (d >>> 2);
260
+
261
+ f = (c & d) | (c & e) | (d & e);
262
+ t = (b << 5) | (b >>> 27);
263
+ a = t + f + a - 1894007588 + blocks[j + 4] << 0;
264
+ c = (c << 30) | (c >>> 2);
265
+ }
266
+
267
+ for(; j < 80; j += 5) {
268
+ f = b ^ c ^ d;
269
+ t = (a << 5) | (a >>> 27);
270
+ e = t + f + e - 899497514 + blocks[j] << 0;
271
+ b = (b << 30) | (b >>> 2);
272
+
273
+ f = a ^ b ^ c;
274
+ t = (e << 5) | (e >>> 27);
275
+ d = t + f + d - 899497514 + blocks[j + 1] << 0;
276
+ a = (a << 30) | (a >>> 2);
277
+
278
+ f = e ^ a ^ b;
279
+ t = (d << 5) | (d >>> 27);
280
+ c = t + f + c - 899497514 + blocks[j + 2] << 0;
281
+ e = (e << 30) | (e >>> 2);
282
+
283
+ f = d ^ e ^ a;
284
+ t = (c << 5) | (c >>> 27);
285
+ b = t + f + b - 899497514 + blocks[j + 3] << 0;
286
+ d = (d << 30) | (d >>> 2);
287
+
288
+ f = c ^ d ^ e;
289
+ t = (b << 5) | (b >>> 27);
290
+ a = t + f + a - 899497514 + blocks[j + 4] << 0;
291
+ c = (c << 30) | (c >>> 2);
292
+ }
293
+
294
+ this.h0 = this.h0 + a << 0;
295
+ this.h1 = this.h1 + b << 0;
296
+ this.h2 = this.h2 + c << 0;
297
+ this.h3 = this.h3 + d << 0;
298
+ this.h4 = this.h4 + e << 0;
299
+ };
300
+
301
+ Sha1.prototype.hex = function () {
302
+ this.finalize();
303
+
304
+ var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;
305
+
306
+ return HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +
307
+ HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +
308
+ HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +
309
+ HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
310
+ HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +
311
+ HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +
312
+ HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +
313
+ HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
314
+ HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +
315
+ HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +
316
+ HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +
317
+ HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
318
+ HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] +
319
+ HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +
320
+ HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +
321
+ HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
322
+ HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] +
323
+ HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] +
324
+ HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] +
325
+ HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F];
326
+ };
327
+
328
+ Sha1.prototype.toString = Sha1.prototype.hex;
329
+
330
+ Sha1.prototype.digest = function () {
331
+ this.finalize();
332
+
333
+ var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4;
334
+
335
+ return [
336
+ (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF,
337
+ (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF,
338
+ (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF,
339
+ (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF,
340
+ (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF
341
+ ];
342
+ };
343
+
344
+ Sha1.prototype.array = Sha1.prototype.digest;
345
+
346
+ Sha1.prototype.arrayBuffer = function () {
347
+ this.finalize();
348
+
349
+ var buffer = new ArrayBuffer(20);
350
+ var dataView = new DataView(buffer);
351
+ dataView.setUint32(0, this.h0);
352
+ dataView.setUint32(4, this.h1);
353
+ dataView.setUint32(8, this.h2);
354
+ dataView.setUint32(12, this.h3);
355
+ dataView.setUint32(16, this.h4);
356
+ return buffer;
357
+ };
358
+
359
+ var exports = createMethod();
360
+
361
+ if (COMMON_JS) {
362
+ module.exports = exports;
363
+ } else {
364
+ root.sha1 = exports;
365
+ if (AMD) {
366
+ define(function () {
367
+ return exports;
368
+ });
369
+ }
370
+ }
371
+ })();
lib_node/WebSocket.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This a Wrapper for the node ws WebSocket implementation to resemble the
2
+ // browser WebSocket implementation better.
3
+ var WebSocket = require("./ws/websocket");
4
+
5
+ class SafeWebSocketClient extends WebSocket {
6
+ constructor(url, protocols) {
7
+ super(url, protocols, { perMessageDeflate: false });
8
+ }
9
+ }
10
+
11
+ module.exports = SafeWebSocketClient;
lib_node/ws/README.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Source used from https://github.com/websockets/ws (MIT licensed)
lib_node/ws/buffer-util.js ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const { EMPTY_BUFFER } = require('./constants');
4
+
5
+ /**
6
+ * Merges an array of buffers into a new buffer.
7
+ *
8
+ * @param {Buffer[]} list The array of buffers to concat
9
+ * @param {Number} totalLength The total length of buffers in the list
10
+ * @return {Buffer} The resulting buffer
11
+ * @public
12
+ */
13
+ function concat(list, totalLength) {
14
+ if (list.length === 0) return EMPTY_BUFFER;
15
+ if (list.length === 1) return list[0];
16
+
17
+ const target = Buffer.allocUnsafe(totalLength);
18
+ let offset = 0;
19
+
20
+ for (let i = 0; i < list.length; i++) {
21
+ const buf = list[i];
22
+ target.set(buf, offset);
23
+ offset += buf.length;
24
+ }
25
+
26
+ return target;
27
+ }
28
+
29
+ /**
30
+ * Masks a buffer using the given mask.
31
+ *
32
+ * @param {Buffer} source The buffer to mask
33
+ * @param {Buffer} mask The mask to use
34
+ * @param {Buffer} output The buffer where to store the result
35
+ * @param {Number} offset The offset at which to start writing
36
+ * @param {Number} length The number of bytes to mask.
37
+ * @public
38
+ */
39
+ function _mask(source, mask, output, offset, length) {
40
+ for (let i = 0; i < length; i++) {
41
+ output[offset + i] = source[i] ^ mask[i & 3];
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Unmasks a buffer using the given mask.
47
+ *
48
+ * @param {Buffer} buffer The buffer to unmask
49
+ * @param {Buffer} mask The mask to use
50
+ * @public
51
+ */
52
+ function _unmask(buffer, mask) {
53
+ // Required until https://github.com/nodejs/node/issues/9006 is resolved.
54
+ const length = buffer.length;
55
+ for (let i = 0; i < length; i++) {
56
+ buffer[i] ^= mask[i & 3];
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Converts a buffer to an `ArrayBuffer`.
62
+ *
63
+ * @param {Buffer} buf The buffer to convert
64
+ * @return {ArrayBuffer} Converted buffer
65
+ * @public
66
+ */
67
+ function toArrayBuffer(buf) {
68
+ if (buf.byteLength === buf.buffer.byteLength) {
69
+ return buf.buffer;
70
+ }
71
+
72
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
73
+ }
74
+
75
+ /**
76
+ * Converts `data` to a `Buffer`.
77
+ *
78
+ * @param {*} data The data to convert
79
+ * @return {Buffer} The buffer
80
+ * @throws {TypeError}
81
+ * @public
82
+ */
83
+ function toBuffer(data) {
84
+ toBuffer.readOnly = true;
85
+
86
+ if (Buffer.isBuffer(data)) return data;
87
+
88
+ let buf;
89
+
90
+ if (data instanceof ArrayBuffer) {
91
+ buf = Buffer.from(data);
92
+ } else if (ArrayBuffer.isView(data)) {
93
+ buf = viewToBuffer(data);
94
+ } else {
95
+ buf = Buffer.from(data);
96
+ toBuffer.readOnly = false;
97
+ }
98
+
99
+ return buf;
100
+ }
101
+
102
+ /**
103
+ * Converts an `ArrayBuffer` view into a buffer.
104
+ *
105
+ * @param {(DataView|TypedArray)} view The view to convert
106
+ * @return {Buffer} Converted view
107
+ * @private
108
+ */
109
+ function viewToBuffer(view) {
110
+ const buf = Buffer.from(view.buffer);
111
+
112
+ if (view.byteLength !== view.buffer.byteLength) {
113
+ return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
114
+ }
115
+
116
+ return buf;
117
+ }
118
+
119
+ try {
120
+ const bufferUtil = require('bufferutil');
121
+ const bu = bufferUtil.BufferUtil || bufferUtil;
122
+
123
+ module.exports = {
124
+ concat,
125
+ mask(source, mask, output, offset, length) {
126
+ if (length < 48) _mask(source, mask, output, offset, length);
127
+ else bu.mask(source, mask, output, offset, length);
128
+ },
129
+ toArrayBuffer,
130
+ toBuffer,
131
+ unmask(buffer, mask) {
132
+ if (buffer.length < 32) _unmask(buffer, mask);
133
+ else bu.unmask(buffer, mask);
134
+ }
135
+ };
136
+ } catch (e) /* istanbul ignore next */ {
137
+ module.exports = {
138
+ concat,
139
+ mask: _mask,
140
+ toArrayBuffer,
141
+ toBuffer,
142
+ unmask: _unmask
143
+ };
144
+ }
lib_node/ws/constants.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
5
+ GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
6
+ kStatusCode: Symbol('status-code'),
7
+ kWebSocket: Symbol('websocket'),
8
+ EMPTY_BUFFER: Buffer.alloc(0),
9
+ NOOP: () => {}
10
+ };
lib_node/ws/event-target.js ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ /**
4
+ * Class representing an event.
5
+ *
6
+ * @private
7
+ */
8
+ class Event {
9
+ /**
10
+ * Create a new `Event`.
11
+ *
12
+ * @param {String} type The name of the event
13
+ * @param {Object} target A reference to the target to which the event was dispatched
14
+ */
15
+ constructor(type, target) {
16
+ this.target = target;
17
+ this.type = type;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Class representing a message event.
23
+ *
24
+ * @extends Event
25
+ * @private
26
+ */
27
+ class MessageEvent extends Event {
28
+ /**
29
+ * Create a new `MessageEvent`.
30
+ *
31
+ * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
32
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
33
+ */
34
+ constructor(data, target) {
35
+ super('message', target);
36
+
37
+ this.data = data;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Class representing a close event.
43
+ *
44
+ * @extends Event
45
+ * @private
46
+ */
47
+ class CloseEvent extends Event {
48
+ /**
49
+ * Create a new `CloseEvent`.
50
+ *
51
+ * @param {Number} code The status code explaining why the connection is being closed
52
+ * @param {String} reason A human-readable string explaining why the connection is closing
53
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
54
+ */
55
+ constructor(code, reason, target) {
56
+ super('close', target);
57
+
58
+ this.wasClean = target._closeFrameReceived && target._closeFrameSent;
59
+ this.reason = reason;
60
+ this.code = code;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Class representing an open event.
66
+ *
67
+ * @extends Event
68
+ * @private
69
+ */
70
+ class OpenEvent extends Event {
71
+ /**
72
+ * Create a new `OpenEvent`.
73
+ *
74
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
75
+ */
76
+ constructor(target) {
77
+ super('open', target);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Class representing an error event.
83
+ *
84
+ * @extends Event
85
+ * @private
86
+ */
87
+ class ErrorEvent extends Event {
88
+ /**
89
+ * Create a new `ErrorEvent`.
90
+ *
91
+ * @param {Object} error The error that generated this event
92
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
93
+ */
94
+ constructor(error, target) {
95
+ super('error', target);
96
+
97
+ this.message = error.message;
98
+ this.error = error;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * This provides methods for emulating the `EventTarget` interface. It's not
104
+ * meant to be used directly.
105
+ *
106
+ * @mixin
107
+ */
108
+ const EventTarget = {
109
+ /**
110
+ * Register an event listener.
111
+ *
112
+ * @param {String} method A string representing the event type to listen for
113
+ * @param {Function} listener The listener to add
114
+ * @public
115
+ */
116
+ addEventListener(method, listener) {
117
+ if (typeof listener !== 'function') return;
118
+
119
+ function onMessage(data) {
120
+ listener.call(this, new MessageEvent(data, this));
121
+ }
122
+
123
+ function onClose(code, message) {
124
+ listener.call(this, new CloseEvent(code, message, this));
125
+ }
126
+
127
+ function onError(error) {
128
+ listener.call(this, new ErrorEvent(error, this));
129
+ }
130
+
131
+ function onOpen() {
132
+ listener.call(this, new OpenEvent(this));
133
+ }
134
+
135
+ if (method === 'message') {
136
+ onMessage._listener = listener;
137
+ this.on(method, onMessage);
138
+ } else if (method === 'close') {
139
+ onClose._listener = listener;
140
+ this.on(method, onClose);
141
+ } else if (method === 'error') {
142
+ onError._listener = listener;
143
+ this.on(method, onError);
144
+ } else if (method === 'open') {
145
+ onOpen._listener = listener;
146
+ this.on(method, onOpen);
147
+ } else {
148
+ this.on(method, listener);
149
+ }
150
+ },
151
+
152
+ /**
153
+ * Remove an event listener.
154
+ *
155
+ * @param {String} method A string representing the event type to remove
156
+ * @param {Function} listener The listener to remove
157
+ * @public
158
+ */
159
+ removeEventListener(method, listener) {
160
+ const listeners = this.listeners(method);
161
+
162
+ for (let i = 0; i < listeners.length; i++) {
163
+ if (listeners[i] === listener || listeners[i]._listener === listener) {
164
+ this.removeListener(method, listeners[i]);
165
+ }
166
+ }
167
+ }
168
+ };
169
+
170
+ module.exports = EventTarget;
lib_node/ws/extension.js ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ //
4
+ // Allowed token characters:
5
+ //
6
+ // '!', '#', '$', '%', '&', ''', '*', '+', '-',
7
+ // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
8
+ //
9
+ // tokenChars[32] === 0 // ' '
10
+ // tokenChars[33] === 1 // '!'
11
+ // tokenChars[34] === 0 // '"'
12
+ // ...
13
+ //
14
+ // prettier-ignore
15
+ const tokenChars = [
16
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
17
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
18
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
19
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
20
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
21
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
22
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
23
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
24
+ ];
25
+
26
+ /**
27
+ * Adds an offer to the map of extension offers or a parameter to the map of
28
+ * parameters.
29
+ *
30
+ * @param {Object} dest The map of extension offers or parameters
31
+ * @param {String} name The extension or parameter name
32
+ * @param {(Object|Boolean|String)} elem The extension parameters or the
33
+ * parameter value
34
+ * @private
35
+ */
36
+ function push(dest, name, elem) {
37
+ if (dest[name] === undefined) dest[name] = [elem];
38
+ else dest[name].push(elem);
39
+ }
40
+
41
+ /**
42
+ * Parses the `Sec-WebSocket-Extensions` header into an object.
43
+ *
44
+ * @param {String} header The field value of the header
45
+ * @return {Object} The parsed object
46
+ * @public
47
+ */
48
+ function parse(header) {
49
+ const offers = Object.create(null);
50
+
51
+ if (header === undefined || header === '') return offers;
52
+
53
+ let params = Object.create(null);
54
+ let mustUnescape = false;
55
+ let isEscaping = false;
56
+ let inQuotes = false;
57
+ let extensionName;
58
+ let paramName;
59
+ let start = -1;
60
+ let end = -1;
61
+ let i = 0;
62
+
63
+ for (; i < header.length; i++) {
64
+ const code = header.charCodeAt(i);
65
+
66
+ if (extensionName === undefined) {
67
+ if (end === -1 && tokenChars[code] === 1) {
68
+ if (start === -1) start = i;
69
+ } else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) {
70
+ if (end === -1 && start !== -1) end = i;
71
+ } else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
72
+ if (start === -1) {
73
+ throw new SyntaxError(`Unexpected character at index ${i}`);
74
+ }
75
+
76
+ if (end === -1) end = i;
77
+ const name = header.slice(start, end);
78
+ if (code === 0x2c) {
79
+ push(offers, name, params);
80
+ params = Object.create(null);
81
+ } else {
82
+ extensionName = name;
83
+ }
84
+
85
+ start = end = -1;
86
+ } else {
87
+ throw new SyntaxError(`Unexpected character at index ${i}`);
88
+ }
89
+ } else if (paramName === undefined) {
90
+ if (end === -1 && tokenChars[code] === 1) {
91
+ if (start === -1) start = i;
92
+ } else if (code === 0x20 || code === 0x09) {
93
+ if (end === -1 && start !== -1) end = i;
94
+ } else if (code === 0x3b || code === 0x2c) {
95
+ if (start === -1) {
96
+ throw new SyntaxError(`Unexpected character at index ${i}`);
97
+ }
98
+
99
+ if (end === -1) end = i;
100
+ push(params, header.slice(start, end), true);
101
+ if (code === 0x2c) {
102
+ push(offers, extensionName, params);
103
+ params = Object.create(null);
104
+ extensionName = undefined;
105
+ }
106
+
107
+ start = end = -1;
108
+ } else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) {
109
+ paramName = header.slice(start, i);
110
+ start = end = -1;
111
+ } else {
112
+ throw new SyntaxError(`Unexpected character at index ${i}`);
113
+ }
114
+ } else {
115
+ //
116
+ // The value of a quoted-string after unescaping must conform to the
117
+ // token ABNF, so only token characters are valid.
118
+ // Ref: https://tools.ietf.org/html/rfc6455#section-9.1
119
+ //
120
+ if (isEscaping) {
121
+ if (tokenChars[code] !== 1) {
122
+ throw new SyntaxError(`Unexpected character at index ${i}`);
123
+ }
124
+ if (start === -1) start = i;
125
+ else if (!mustUnescape) mustUnescape = true;
126
+ isEscaping = false;
127
+ } else if (inQuotes) {
128
+ if (tokenChars[code] === 1) {
129
+ if (start === -1) start = i;
130
+ } else if (code === 0x22 /* '"' */ && start !== -1) {
131
+ inQuotes = false;
132
+ end = i;
133
+ } else if (code === 0x5c /* '\' */) {
134
+ isEscaping = true;
135
+ } else {
136
+ throw new SyntaxError(`Unexpected character at index ${i}`);
137
+ }
138
+ } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
139
+ inQuotes = true;
140
+ } else if (end === -1 && tokenChars[code] === 1) {
141
+ if (start === -1) start = i;
142
+ } else if (start !== -1 && (code === 0x20 || code === 0x09)) {
143
+ if (end === -1) end = i;
144
+ } else if (code === 0x3b || code === 0x2c) {
145
+ if (start === -1) {
146
+ throw new SyntaxError(`Unexpected character at index ${i}`);
147
+ }
148
+
149
+ if (end === -1) end = i;
150
+ let value = header.slice(start, end);
151
+ if (mustUnescape) {
152
+ value = value.replace(/\\/g, '');
153
+ mustUnescape = false;
154
+ }
155
+ push(params, paramName, value);
156
+ if (code === 0x2c) {
157
+ push(offers, extensionName, params);
158
+ params = Object.create(null);
159
+ extensionName = undefined;
160
+ }
161
+
162
+ paramName = undefined;
163
+ start = end = -1;
164
+ } else {
165
+ throw new SyntaxError(`Unexpected character at index ${i}`);
166
+ }
167
+ }
168
+ }
169
+
170
+ if (start === -1 || inQuotes) {
171
+ throw new SyntaxError('Unexpected end of input');
172
+ }
173
+
174
+ if (end === -1) end = i;
175
+ const token = header.slice(start, end);
176
+ if (extensionName === undefined) {
177
+ push(offers, token, params);
178
+ } else {
179
+ if (paramName === undefined) {
180
+ push(params, token, true);
181
+ } else if (mustUnescape) {
182
+ push(params, paramName, token.replace(/\\/g, ''));
183
+ } else {
184
+ push(params, paramName, token);
185
+ }
186
+ push(offers, extensionName, params);
187
+ }
188
+
189
+ return offers;
190
+ }
191
+
192
+ /**
193
+ * Builds the `Sec-WebSocket-Extensions` header field value.
194
+ *
195
+ * @param {Object} extensions The map of extensions and parameters to format
196
+ * @return {String} A string representing the given object
197
+ * @public
198
+ */
199
+ function format(extensions) {
200
+ return Object.keys(extensions)
201
+ .map((extension) => {
202
+ let configurations = extensions[extension];
203
+ if (!Array.isArray(configurations)) configurations = [configurations];
204
+ return configurations
205
+ .map((params) => {
206
+ return [extension]
207
+ .concat(
208
+ Object.keys(params).map((k) => {
209
+ let values = params[k];
210
+ if (!Array.isArray(values)) values = [values];
211
+ return values
212
+ .map((v) => (v === true ? k : `${k}=${v}`))
213
+ .join('; ');
214
+ })
215
+ )
216
+ .join('; ');
217
+ })
218
+ .join(', ');
219
+ })
220
+ .join(', ');
221
+ }
222
+
223
+ module.exports = { format, parse };
lib_node/ws/permessage-deflate.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // ErikOnBike
2
+ // The original code is removed since its usage might degrade performance and memory usage.
3
+ module.exports = {
4
+ extensionName: "PerMessageDeflate"
5
+ };
lib_node/ws/receiver.js ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const { Writable } = require('stream');
4
+
5
+ const PerMessageDeflate = require('./permessage-deflate');
6
+ const {
7
+ BINARY_TYPES,
8
+ EMPTY_BUFFER,
9
+ kStatusCode,
10
+ kWebSocket
11
+ } = require('./constants');
12
+ const { concat, toArrayBuffer, unmask } = require('./buffer-util');
13
+ const { isValidStatusCode, isValidUTF8 } = require('./validation');
14
+
15
+ const GET_INFO = 0;
16
+ const GET_PAYLOAD_LENGTH_16 = 1;
17
+ const GET_PAYLOAD_LENGTH_64 = 2;
18
+ const GET_MASK = 3;
19
+ const GET_DATA = 4;
20
+ const INFLATING = 5;
21
+
22
+ /**
23
+ * HyBi Receiver implementation.
24
+ *
25
+ * @extends stream.Writable
26
+ */
27
+ class Receiver extends Writable {
28
+ /**
29
+ * Creates a Receiver instance.
30
+ *
31
+ * @param {String} binaryType The type for binary data
32
+ * @param {Object} extensions An object containing the negotiated extensions
33
+ * @param {Number} maxPayload The maximum allowed message length
34
+ */
35
+ constructor(binaryType, extensions, maxPayload) {
36
+ super();
37
+
38
+ this._binaryType = binaryType || BINARY_TYPES[0];
39
+ this[kWebSocket] = undefined;
40
+ this._extensions = extensions || {};
41
+ this._maxPayload = maxPayload | 0;
42
+
43
+ this._bufferedBytes = 0;
44
+ this._buffers = [];
45
+
46
+ this._compressed = false;
47
+ this._payloadLength = 0;
48
+ this._mask = undefined;
49
+ this._fragmented = 0;
50
+ this._masked = false;
51
+ this._fin = false;
52
+ this._opcode = 0;
53
+
54
+ this._totalPayloadLength = 0;
55
+ this._messageLength = 0;
56
+ this._fragments = [];
57
+
58
+ this._state = GET_INFO;
59
+ this._loop = false;
60
+ }
61
+
62
+ /**
63
+ * Implements `Writable.prototype._write()`.
64
+ *
65
+ * @param {Buffer} chunk The chunk of data to write
66
+ * @param {String} encoding The character encoding of `chunk`
67
+ * @param {Function} cb Callback
68
+ */
69
+ _write(chunk, encoding, cb) {
70
+ if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
71
+
72
+ this._bufferedBytes += chunk.length;
73
+ this._buffers.push(chunk);
74
+ this.startLoop(cb);
75
+ }
76
+
77
+ /**
78
+ * Consumes `n` bytes from the buffered data.
79
+ *
80
+ * @param {Number} n The number of bytes to consume
81
+ * @return {Buffer} The consumed bytes
82
+ * @private
83
+ */
84
+ consume(n) {
85
+ this._bufferedBytes -= n;
86
+
87
+ if (n === this._buffers[0].length) return this._buffers.shift();
88
+
89
+ if (n < this._buffers[0].length) {
90
+ const buf = this._buffers[0];
91
+ this._buffers[0] = buf.slice(n);
92
+ return buf.slice(0, n);
93
+ }
94
+
95
+ const dst = Buffer.allocUnsafe(n);
96
+
97
+ do {
98
+ const buf = this._buffers[0];
99
+ const offset = dst.length - n;
100
+
101
+ if (n >= buf.length) {
102
+ dst.set(this._buffers.shift(), offset);
103
+ } else {
104
+ dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
105
+ this._buffers[0] = buf.slice(n);
106
+ }
107
+
108
+ n -= buf.length;
109
+ } while (n > 0);
110
+
111
+ return dst;
112
+ }
113
+
114
+ /**
115
+ * Starts the parsing loop.
116
+ *
117
+ * @param {Function} cb Callback
118
+ * @private
119
+ */
120
+ startLoop(cb) {
121
+ let err;
122
+ this._loop = true;
123
+
124
+ do {
125
+ switch (this._state) {
126
+ case GET_INFO:
127
+ err = this.getInfo();
128
+ break;
129
+ case GET_PAYLOAD_LENGTH_16:
130
+ err = this.getPayloadLength16();
131
+ break;
132
+ case GET_PAYLOAD_LENGTH_64:
133
+ err = this.getPayloadLength64();
134
+ break;
135
+ case GET_MASK:
136
+ this.getMask();
137
+ break;
138
+ case GET_DATA:
139
+ err = this.getData(cb);
140
+ break;
141
+ default:
142
+ // `INFLATING`
143
+ this._loop = false;
144
+ return;
145
+ }
146
+ } while (this._loop);
147
+
148
+ cb(err);
149
+ }
150
+
151
+ /**
152
+ * Reads the first two bytes of a frame.
153
+ *
154
+ * @return {(RangeError|undefined)} A possible error
155
+ * @private
156
+ */
157
+ getInfo() {
158
+ if (this._bufferedBytes < 2) {
159
+ this._loop = false;
160
+ return;
161
+ }
162
+
163
+ const buf = this.consume(2);
164
+
165
+ if ((buf[0] & 0x30) !== 0x00) {
166
+ this._loop = false;
167
+ return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002);
168
+ }
169
+
170
+ const compressed = (buf[0] & 0x40) === 0x40;
171
+
172
+ if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
173
+ this._loop = false;
174
+ return error(RangeError, 'RSV1 must be clear', true, 1002);
175
+ }
176
+
177
+ this._fin = (buf[0] & 0x80) === 0x80;
178
+ this._opcode = buf[0] & 0x0f;
179
+ this._payloadLength = buf[1] & 0x7f;
180
+
181
+ if (this._opcode === 0x00) {
182
+ if (compressed) {
183
+ this._loop = false;
184
+ return error(RangeError, 'RSV1 must be clear', true, 1002);
185
+ }
186
+
187
+ if (!this._fragmented) {
188
+ this._loop = false;
189
+ return error(RangeError, 'invalid opcode 0', true, 1002);
190
+ }
191
+
192
+ this._opcode = this._fragmented;
193
+ } else if (this._opcode === 0x01 || this._opcode === 0x02) {
194
+ if (this._fragmented) {
195
+ this._loop = false;
196
+ return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
197
+ }
198
+
199
+ this._compressed = compressed;
200
+ } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
201
+ if (!this._fin) {
202
+ this._loop = false;
203
+ return error(RangeError, 'FIN must be set', true, 1002);
204
+ }
205
+
206
+ if (compressed) {
207
+ this._loop = false;
208
+ return error(RangeError, 'RSV1 must be clear', true, 1002);
209
+ }
210
+
211
+ if (this._payloadLength > 0x7d) {
212
+ this._loop = false;
213
+ return error(
214
+ RangeError,
215
+ `invalid payload length ${this._payloadLength}`,
216
+ true,
217
+ 1002
218
+ );
219
+ }
220
+ } else {
221
+ this._loop = false;
222
+ return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
223
+ }
224
+
225
+ if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
226
+ this._masked = (buf[1] & 0x80) === 0x80;
227
+
228
+ if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
229
+ else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
230
+ else return this.haveLength();
231
+ }
232
+
233
+ /**
234
+ * Gets extended payload length (7+16).
235
+ *
236
+ * @return {(RangeError|undefined)} A possible error
237
+ * @private
238
+ */
239
+ getPayloadLength16() {
240
+ if (this._bufferedBytes < 2) {
241
+ this._loop = false;
242
+ return;
243
+ }
244
+
245
+ this._payloadLength = this.consume(2).readUInt16BE(0);
246
+ return this.haveLength();
247
+ }
248
+
249
+ /**
250
+ * Gets extended payload length (7+64).
251
+ *
252
+ * @return {(RangeError|undefined)} A possible error
253
+ * @private
254
+ */
255
+ getPayloadLength64() {
256
+ if (this._bufferedBytes < 8) {
257
+ this._loop = false;
258
+ return;
259
+ }
260
+
261
+ const buf = this.consume(8);
262
+ const num = buf.readUInt32BE(0);
263
+
264
+ //
265
+ // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
266
+ // if payload length is greater than this number.
267
+ //
268
+ if (num > Math.pow(2, 53 - 32) - 1) {
269
+ this._loop = false;
270
+ return error(
271
+ RangeError,
272
+ 'Unsupported WebSocket frame: payload length > 2^53 - 1',
273
+ false,
274
+ 1009
275
+ );
276
+ }
277
+
278
+ this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
279
+ return this.haveLength();
280
+ }
281
+
282
+ /**
283
+ * Payload length has been read.
284
+ *
285
+ * @return {(RangeError|undefined)} A possible error
286
+ * @private
287
+ */
288
+ haveLength() {
289
+ if (this._payloadLength && this._opcode < 0x08) {
290
+ this._totalPayloadLength += this._payloadLength;
291
+ if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
292
+ this._loop = false;
293
+ return error(RangeError, 'Max payload size exceeded', false, 1009);
294
+ }
295
+ }
296
+
297
+ if (this._masked) this._state = GET_MASK;
298
+ else this._state = GET_DATA;
299
+ }
300
+
301
+ /**
302
+ * Reads mask bytes.
303
+ *
304
+ * @private
305
+ */
306
+ getMask() {
307
+ if (this._bufferedBytes < 4) {
308
+ this._loop = false;
309
+ return;
310
+ }
311
+
312
+ this._mask = this.consume(4);
313
+ this._state = GET_DATA;
314
+ }
315
+
316
+ /**
317
+ * Reads data bytes.
318
+ *
319
+ * @param {Function} cb Callback
320
+ * @return {(Error|RangeError|undefined)} A possible error
321
+ * @private
322
+ */
323
+ getData(cb) {
324
+ let data = EMPTY_BUFFER;
325
+
326
+ if (this._payloadLength) {
327
+ if (this._bufferedBytes < this._payloadLength) {
328
+ this._loop = false;
329
+ return;
330
+ }
331
+
332
+ data = this.consume(this._payloadLength);
333
+ if (this._masked) unmask(data, this._mask);
334
+ }
335
+
336
+ if (this._opcode > 0x07) return this.controlMessage(data);
337
+
338
+ if (this._compressed) {
339
+ this._state = INFLATING;
340
+ this.decompress(data, cb);
341
+ return;
342
+ }
343
+
344
+ if (data.length) {
345
+ //
346
+ // This message is not compressed so its lenght is the sum of the payload
347
+ // length of all fragments.
348
+ //
349
+ this._messageLength = this._totalPayloadLength;
350
+ this._fragments.push(data);
351
+ }
352
+
353
+ return this.dataMessage();
354
+ }
355
+
356
+ /**
357
+ * Decompresses data.
358
+ *
359
+ * @param {Buffer} data Compressed data
360
+ * @param {Function} cb Callback
361
+ * @private
362
+ */
363
+ decompress(data, cb) {
364
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
365
+
366
+ perMessageDeflate.decompress(data, this._fin, (err, buf) => {
367
+ if (err) return cb(err);
368
+
369
+ if (buf.length) {
370
+ this._messageLength += buf.length;
371
+ if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
372
+ return cb(
373
+ error(RangeError, 'Max payload size exceeded', false, 1009)
374
+ );
375
+ }
376
+
377
+ this._fragments.push(buf);
378
+ }
379
+
380
+ const er = this.dataMessage();
381
+ if (er) return cb(er);
382
+
383
+ this.startLoop(cb);
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Handles a data message.
389
+ *
390
+ * @return {(Error|undefined)} A possible error
391
+ * @private
392
+ */
393
+ dataMessage() {
394
+ if (this._fin) {
395
+ const messageLength = this._messageLength;
396
+ const fragments = this._fragments;
397
+
398
+ this._totalPayloadLength = 0;
399
+ this._messageLength = 0;
400
+ this._fragmented = 0;
401
+ this._fragments = [];
402
+
403
+ if (this._opcode === 2) {
404
+ let data;
405
+
406
+ if (this._binaryType === 'nodebuffer') {
407
+ data = concat(fragments, messageLength);
408
+ } else if (this._binaryType === 'arraybuffer') {
409
+ data = toArrayBuffer(concat(fragments, messageLength));
410
+ } else {
411
+ data = fragments;
412
+ }
413
+
414
+ this.emit('message', data);
415
+ } else {
416
+ const buf = concat(fragments, messageLength);
417
+
418
+ if (!isValidUTF8(buf)) {
419
+ this._loop = false;
420
+ return error(Error, 'invalid UTF-8 sequence', true, 1007);
421
+ }
422
+
423
+ this.emit('message', buf.toString());
424
+ }
425
+ }
426
+
427
+ this._state = GET_INFO;
428
+ }
429
+
430
+ /**
431
+ * Handles a control message.
432
+ *
433
+ * @param {Buffer} data Data to handle
434
+ * @return {(Error|RangeError|undefined)} A possible error
435
+ * @private
436
+ */
437
+ controlMessage(data) {
438
+ if (this._opcode === 0x08) {
439
+ this._loop = false;
440
+
441
+ if (data.length === 0) {
442
+ this.emit('conclude', 1005, '');
443
+ this.end();
444
+ } else if (data.length === 1) {
445
+ return error(RangeError, 'invalid payload length 1', true, 1002);
446
+ } else {
447
+ const code = data.readUInt16BE(0);
448
+
449
+ if (!isValidStatusCode(code)) {
450
+ return error(RangeError, `invalid status code ${code}`, true, 1002);
451
+ }
452
+
453
+ const buf = data.slice(2);
454
+
455
+ if (!isValidUTF8(buf)) {
456
+ return error(Error, 'invalid UTF-8 sequence', true, 1007);
457
+ }
458
+
459
+ this.emit('conclude', code, buf.toString());
460
+ this.end();
461
+ }
462
+ } else if (this._opcode === 0x09) {
463
+ this.emit('ping', data);
464
+ } else {
465
+ this.emit('pong', data);
466
+ }
467
+
468
+ this._state = GET_INFO;
469
+ }
470
+ }
471
+
472
+ module.exports = Receiver;
473
+
474
+ /**
475
+ * Builds an error object.
476
+ *
477
+ * @param {(Error|RangeError)} ErrorCtor The error constructor
478
+ * @param {String} message The error message
479
+ * @param {Boolean} prefix Specifies whether or not to add a default prefix to
480
+ * `message`
481
+ * @param {Number} statusCode The status code
482
+ * @return {(Error|RangeError)} The error
483
+ * @private
484
+ */
485
+ function error(ErrorCtor, message, prefix, statusCode) {
486
+ const err = new ErrorCtor(
487
+ prefix ? `Invalid WebSocket frame: ${message}` : message
488
+ );
489
+
490
+ Error.captureStackTrace(err, error);
491
+ err[kStatusCode] = statusCode;
492
+ return err;
493
+ }
lib_node/ws/sender.js ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const { randomFillSync } = require('crypto');
4
+
5
+ const PerMessageDeflate = require('./permessage-deflate');
6
+ const { EMPTY_BUFFER } = require('./constants');
7
+ const { isValidStatusCode } = require('./validation');
8
+ const { mask: applyMask, toBuffer } = require('./buffer-util');
9
+
10
+ const mask = Buffer.alloc(4);
11
+
12
+ /**
13
+ * HyBi Sender implementation.
14
+ */
15
+ class Sender {
16
+ /**
17
+ * Creates a Sender instance.
18
+ *
19
+ * @param {net.Socket} socket The connection socket
20
+ * @param {Object} extensions An object containing the negotiated extensions
21
+ */
22
+ constructor(socket, extensions) {
23
+ this._extensions = extensions || {};
24
+ this._socket = socket;
25
+
26
+ this._firstFragment = true;
27
+ this._compress = false;
28
+
29
+ this._bufferedBytes = 0;
30
+ this._deflating = false;
31
+ this._queue = [];
32
+ }
33
+
34
+ /**
35
+ * Frames a piece of data according to the HyBi WebSocket protocol.
36
+ *
37
+ * @param {Buffer} data The data to frame
38
+ * @param {Object} options Options object
39
+ * @param {Number} options.opcode The opcode
40
+ * @param {Boolean} options.readOnly Specifies whether `data` can be modified
41
+ * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
42
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
43
+ * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
44
+ * @return {Buffer[]} The framed data as a list of `Buffer` instances
45
+ * @public
46
+ */
47
+ static frame(data, options) {
48
+ const merge = options.mask && options.readOnly;
49
+ let offset = options.mask ? 6 : 2;
50
+ let payloadLength = data.length;
51
+
52
+ if (data.length >= 65536) {
53
+ offset += 8;
54
+ payloadLength = 127;
55
+ } else if (data.length > 125) {
56
+ offset += 2;
57
+ payloadLength = 126;
58
+ }
59
+
60
+ const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
61
+
62
+ target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
63
+ if (options.rsv1) target[0] |= 0x40;
64
+
65
+ target[1] = payloadLength;
66
+
67
+ if (payloadLength === 126) {
68
+ target.writeUInt16BE(data.length, 2);
69
+ } else if (payloadLength === 127) {
70
+ target.writeUInt32BE(0, 2);
71
+ target.writeUInt32BE(data.length, 6);
72
+ }
73
+
74
+ if (!options.mask) return [target, data];
75
+
76
+ randomFillSync(mask, 0, 4);
77
+
78
+ target[1] |= 0x80;
79
+ target[offset - 4] = mask[0];
80
+ target[offset - 3] = mask[1];
81
+ target[offset - 2] = mask[2];
82
+ target[offset - 1] = mask[3];
83
+
84
+ if (merge) {
85
+ applyMask(data, mask, target, offset, data.length);
86
+ return [target];
87
+ }
88
+
89
+ applyMask(data, mask, data, 0, data.length);
90
+ return [target, data];
91
+ }
92
+
93
+ /**
94
+ * Sends a close message to the other peer.
95
+ *
96
+ * @param {(Number|undefined)} code The status code component of the body
97
+ * @param {String} data The message component of the body
98
+ * @param {Boolean} mask Specifies whether or not to mask the message
99
+ * @param {Function} cb Callback
100
+ * @public
101
+ */
102
+ close(code, data, mask, cb) {
103
+ let buf;
104
+
105
+ if (code === undefined) {
106
+ buf = EMPTY_BUFFER;
107
+ } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
108
+ throw new TypeError('First argument must be a valid error code number');
109
+ } else if (data === undefined || data === '') {
110
+ buf = Buffer.allocUnsafe(2);
111
+ buf.writeUInt16BE(code, 0);
112
+ } else {
113
+ buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data));
114
+ buf.writeUInt16BE(code, 0);
115
+ buf.write(data, 2);
116
+ }
117
+
118
+ if (this._deflating) {
119
+ this.enqueue([this.doClose, buf, mask, cb]);
120
+ } else {
121
+ this.doClose(buf, mask, cb);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Frames and sends a close message.
127
+ *
128
+ * @param {Buffer} data The message to send
129
+ * @param {Boolean} mask Specifies whether or not to mask `data`
130
+ * @param {Function} cb Callback
131
+ * @private
132
+ */
133
+ doClose(data, mask, cb) {
134
+ this.sendFrame(
135
+ Sender.frame(data, {
136
+ fin: true,
137
+ rsv1: false,
138
+ opcode: 0x08,
139
+ mask,
140
+ readOnly: false
141
+ }),
142
+ cb
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Sends a ping message to the other peer.
148
+ *
149
+ * @param {*} data The message to send
150
+ * @param {Boolean} mask Specifies whether or not to mask `data`
151
+ * @param {Function} cb Callback
152
+ * @public
153
+ */
154
+ ping(data, mask, cb) {
155
+ const buf = toBuffer(data);
156
+
157
+ if (this._deflating) {
158
+ this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]);
159
+ } else {
160
+ this.doPing(buf, mask, toBuffer.readOnly, cb);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Frames and sends a ping message.
166
+ *
167
+ * @param {*} data The message to send
168
+ * @param {Boolean} mask Specifies whether or not to mask `data`
169
+ * @param {Boolean} readOnly Specifies whether `data` can be modified
170
+ * @param {Function} cb Callback
171
+ * @private
172
+ */
173
+ doPing(data, mask, readOnly, cb) {
174
+ this.sendFrame(
175
+ Sender.frame(data, {
176
+ fin: true,
177
+ rsv1: false,
178
+ opcode: 0x09,
179
+ mask,
180
+ readOnly
181
+ }),
182
+ cb
183
+ );
184
+ }
185
+
186
+ /**
187
+ * Sends a pong message to the other peer.
188
+ *
189
+ * @param {*} data The message to send
190
+ * @param {Boolean} mask Specifies whether or not to mask `data`
191
+ * @param {Function} cb Callback
192
+ * @public
193
+ */
194
+ pong(data, mask, cb) {
195
+ const buf = toBuffer(data);
196
+
197
+ if (this._deflating) {
198
+ this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]);
199
+ } else {
200
+ this.doPong(buf, mask, toBuffer.readOnly, cb);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Frames and sends a pong message.
206
+ *
207
+ * @param {*} data The message to send
208
+ * @param {Boolean} mask Specifies whether or not to mask `data`
209
+ * @param {Boolean} readOnly Specifies whether `data` can be modified
210
+ * @param {Function} cb Callback
211
+ * @private
212
+ */
213
+ doPong(data, mask, readOnly, cb) {
214
+ this.sendFrame(
215
+ Sender.frame(data, {
216
+ fin: true,
217
+ rsv1: false,
218
+ opcode: 0x0a,
219
+ mask,
220
+ readOnly
221
+ }),
222
+ cb
223
+ );
224
+ }
225
+
226
+ /**
227
+ * Sends a data message to the other peer.
228
+ *
229
+ * @param {*} data The message to send
230
+ * @param {Object} options Options object
231
+ * @param {Boolean} options.compress Specifies whether or not to compress `data`
232
+ * @param {Boolean} options.binary Specifies whether `data` is binary or text
233
+ * @param {Boolean} options.fin Specifies whether the fragment is the last one
234
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
235
+ * @param {Function} cb Callback
236
+ * @public
237
+ */
238
+ send(data, options, cb) {
239
+ const buf = toBuffer(data);
240
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
241
+ let opcode = options.binary ? 2 : 1;
242
+ let rsv1 = options.compress;
243
+
244
+ if (this._firstFragment) {
245
+ this._firstFragment = false;
246
+ if (rsv1 && perMessageDeflate) {
247
+ rsv1 = buf.length >= perMessageDeflate._threshold;
248
+ }
249
+ this._compress = rsv1;
250
+ } else {
251
+ rsv1 = false;
252
+ opcode = 0;
253
+ }
254
+
255
+ if (options.fin) this._firstFragment = true;
256
+
257
+ if (perMessageDeflate) {
258
+ const opts = {
259
+ fin: options.fin,
260
+ rsv1,
261
+ opcode,
262
+ mask: options.mask,
263
+ readOnly: toBuffer.readOnly
264
+ };
265
+
266
+ if (this._deflating) {
267
+ this.enqueue([this.dispatch, buf, this._compress, opts, cb]);
268
+ } else {
269
+ this.dispatch(buf, this._compress, opts, cb);
270
+ }
271
+ } else {
272
+ this.sendFrame(
273
+ Sender.frame(buf, {
274
+ fin: options.fin,
275
+ rsv1: false,
276
+ opcode,
277
+ mask: options.mask,
278
+ readOnly: toBuffer.readOnly
279
+ }),
280
+ cb
281
+ );
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Dispatches a data message.
287
+ *
288
+ * @param {Buffer} data The message to send
289
+ * @param {Boolean} compress Specifies whether or not to compress `data`
290
+ * @param {Object} options Options object
291
+ * @param {Number} options.opcode The opcode
292
+ * @param {Boolean} options.readOnly Specifies whether `data` can be modified
293
+ * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
294
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
295
+ * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
296
+ * @param {Function} cb Callback
297
+ * @private
298
+ */
299
+ dispatch(data, compress, options, cb) {
300
+ if (!compress) {
301
+ this.sendFrame(Sender.frame(data, options), cb);
302
+ return;
303
+ }
304
+
305
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
306
+
307
+ this._deflating = true;
308
+ perMessageDeflate.compress(data, options.fin, (_, buf) => {
309
+ this._deflating = false;
310
+ options.readOnly = false;
311
+ this.sendFrame(Sender.frame(buf, options), cb);
312
+ this.dequeue();
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Executes queued send operations.
318
+ *
319
+ * @private
320
+ */
321
+ dequeue() {
322
+ while (!this._deflating && this._queue.length) {
323
+ const params = this._queue.shift();
324
+
325
+ this._bufferedBytes -= params[1].length;
326
+ Reflect.apply(params[0], this, params.slice(1));
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Enqueues a send operation.
332
+ *
333
+ * @param {Array} params Send operation parameters.
334
+ * @private
335
+ */
336
+ enqueue(params) {
337
+ this._bufferedBytes += params[1].length;
338
+ this._queue.push(params);
339
+ }
340
+
341
+ /**
342
+ * Sends a frame.
343
+ *
344
+ * @param {Buffer[]} list The frame to send
345
+ * @param {Function} cb Callback
346
+ * @private
347
+ */
348
+ sendFrame(list, cb) {
349
+ if (list.length === 2) {
350
+ this._socket.cork();
351
+ this._socket.write(list[0]);
352
+ this._socket.write(list[1], cb);
353
+ this._socket.uncork();
354
+ } else {
355
+ this._socket.write(list[0], cb);
356
+ }
357
+ }
358
+ }
359
+
360
+ module.exports = Sender;
lib_node/ws/stream.js ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const { Duplex } = require('stream');
4
+
5
+ /**
6
+ * Emits the `'close'` event on a stream.
7
+ *
8
+ * @param {stream.Duplex} The stream.
9
+ * @private
10
+ */
11
+ function emitClose(stream) {
12
+ stream.emit('close');
13
+ }
14
+
15
+ /**
16
+ * The listener of the `'end'` event.
17
+ *
18
+ * @private
19
+ */
20
+ function duplexOnEnd() {
21
+ if (!this.destroyed && this._writableState.finished) {
22
+ this.destroy();
23
+ }
24
+ }
25
+
26
+ /**
27
+ * The listener of the `'error'` event.
28
+ *
29
+ * @private
30
+ */
31
+ function duplexOnError(err) {
32
+ this.removeListener('error', duplexOnError);
33
+ this.destroy();
34
+ if (this.listenerCount('error') === 0) {
35
+ // Do not suppress the throwing behavior.
36
+ this.emit('error', err);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Wraps a `WebSocket` in a duplex stream.
42
+ *
43
+ * @param {WebSocket} ws The `WebSocket` to wrap
44
+ * @param {Object} options The options for the `Duplex` constructor
45
+ * @return {stream.Duplex} The duplex stream
46
+ * @public
47
+ */
48
+ function createWebSocketStream(ws, options) {
49
+ let resumeOnReceiverDrain = true;
50
+
51
+ function receiverOnDrain() {
52
+ if (resumeOnReceiverDrain) ws._socket.resume();
53
+ }
54
+
55
+ if (ws.readyState === ws.CONNECTING) {
56
+ ws.once('open', function open() {
57
+ ws._receiver.removeAllListeners('drain');
58
+ ws._receiver.on('drain', receiverOnDrain);
59
+ });
60
+ } else {
61
+ ws._receiver.removeAllListeners('drain');
62
+ ws._receiver.on('drain', receiverOnDrain);
63
+ }
64
+
65
+ const duplex = new Duplex({
66
+ ...options,
67
+ autoDestroy: false,
68
+ emitClose: false,
69
+ objectMode: false,
70
+ readableObjectMode: false,
71
+ writableObjectMode: false
72
+ });
73
+
74
+ ws.on('message', function message(msg) {
75
+ if (!duplex.push(msg)) {
76
+ resumeOnReceiverDrain = false;
77
+ ws._socket.pause();
78
+ }
79
+ });
80
+
81
+ ws.once('error', function error(err) {
82
+ duplex.destroy(err);
83
+ });
84
+
85
+ ws.once('close', function close() {
86
+ if (duplex.destroyed) return;
87
+
88
+ duplex.push(null);
89
+ });
90
+
91
+ duplex._destroy = function(err, callback) {
92
+ if (ws.readyState === ws.CLOSED) {
93
+ callback(err);
94
+ process.nextTick(emitClose, duplex);
95
+ return;
96
+ }
97
+
98
+ ws.once('close', function close() {
99
+ callback(err);
100
+ process.nextTick(emitClose, duplex);
101
+ });
102
+ ws.terminate();
103
+ };
104
+
105
+ duplex._final = function(callback) {
106
+ if (ws.readyState === ws.CONNECTING) {
107
+ ws.once('open', function open() {
108
+ duplex._final(callback);
109
+ });
110
+ return;
111
+ }
112
+
113
+ if (ws._socket._writableState.finished) {
114
+ if (duplex._readableState.endEmitted) duplex.destroy();
115
+ callback();
116
+ } else {
117
+ ws._socket.once('finish', function finish() {
118
+ // `duplex` is not destroyed here because the `'end'` event will be
119
+ // emitted on `duplex` after this `'finish'` event. The EOF signaling
120
+ // `null` chunk is, in fact, pushed when the WebSocket emits `'close'`.
121
+ callback();
122
+ });
123
+ ws.close();
124
+ }
125
+ };
126
+
127
+ duplex._read = function() {
128
+ if (ws.readyState === ws.OPEN && !resumeOnReceiverDrain) {
129
+ resumeOnReceiverDrain = true;
130
+ if (!ws._receiver._writableState.needDrain) ws._socket.resume();
131
+ }
132
+ };
133
+
134
+ duplex._write = function(chunk, encoding, callback) {
135
+ if (ws.readyState === ws.CONNECTING) {
136
+ ws.once('open', function open() {
137
+ duplex._write(chunk, encoding, callback);
138
+ });
139
+ return;
140
+ }
141
+
142
+ ws.send(chunk, callback);
143
+ };
144
+
145
+ duplex.on('end', duplexOnEnd);
146
+ duplex.on('error', duplexOnError);
147
+ return duplex;
148
+ }
149
+
150
+ module.exports = createWebSocketStream;