Spaces:
Running
Running
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +5 -0
- .gitignore +5 -0
- .gitpod.yml +2 -0
- .nvmrc +1 -0
- .travis.yml +21 -0
- LICENSE.md +22 -0
- README.md +229 -10
- benchmark/benchmark.css +36 -0
- benchmark/benchmark.html +41 -0
- benchmark/benchmark.image +3 -0
- benchmark/benchmark.js +72 -0
- demo/JSBridge.st +1 -0
- demo/SimplePlugin.js +61 -0
- demo/icon.png +0 -0
- demo/index.html +46 -0
- demo/mini.image +3 -0
- demo/simple.css +38 -0
- demo/simple.html +46 -0
- demo/simple.js +34 -0
- demo/squeakjs.changes +0 -0
- demo/squeakjs.image +3 -0
- dist/squeak_bundle.js +0 -0
- dist/squeak_headless_bundle.js +0 -0
- etoys/etoys.css +38 -0
- etoys/etoys.js +43 -0
- etoys/etoys.png +0 -0
- etoys/index.html +45 -0
- ffi/libc.js +71 -0
- ffi/opengl.js +0 -0
- globals.js +90 -0
- headless/headless.image +3 -0
- headless/index.html +20 -0
- jit.js +1169 -0
- lib/FileSaver.js +182 -0
- lib/addtohomescreen.css +257 -0
- lib/addtohomescreen.js +725 -0
- lib/gh-fork-ribbon.css +140 -0
- lib/jszip.js +0 -0
- lib/lz-string.js +665 -0
- lib/sha1.js +371 -0
- lib_node/WebSocket.js +11 -0
- lib_node/ws/README.txt +1 -0
- lib_node/ws/buffer-util.js +144 -0
- lib_node/ws/constants.js +10 -0
- lib_node/ws/event-target.js +170 -0
- lib_node/ws/extension.js +223 -0
- lib_node/ws/permessage-deflate.js +5 -0
- lib_node/ws/receiver.js +493 -0
- lib_node/ws/sender.js +360 -0
- lib_node/ws/stream.js +150 -0
.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 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|