mirror of
https://github.com/kiwix/kiwix-js-pwa.git
synced 2025-09-08 03:37:12 -04:00
Implement WASM with fallback (#174)
Former-commit-id: c68596bc74e187de64dc2e39e43eadcf1f9ea32f [formerly fa761722a44f952ce2dd17b8c9944b2b4f83adbf] [formerly f3cc4ecfb825af85e764ab89d1b350cdaca4b5e2] [formerly 2ce10066e6d7b53aa1390d7e75189f18c7b59b8a [formerly e7fe66c76d904f511bfa246c059dade2d12911b6 [formerly 40d3b58c2cf6821c94cb138f86d45c498698beed]]] Former-commit-id: 375cfb19d2c3ca7fcda82adff21837dcaecc2e82 [formerly a9650d1a4d00e1bc0ad8c82cb360c7c79cae2173 [formerly 7d6ea7d5e4332e09c8aa04e00d50c402a6ae0d16]] Former-commit-id: cba19c61686b992d81f8736997ed56ad669ea3cd [formerly 34f89f43b796b52dc5ff580d4f481a7d1b5ef7fc] Former-commit-id: a769646441e4eb51a6ee4485f594d46a98d1ce93
This commit is contained in:
parent
3bee578801
commit
9728d41fc9
43
.gitattributes
vendored
Normal file
43
.gitattributes
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text eol=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
*.png binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
*.dat binary
|
||||
*.idx binary
|
||||
*.zip binary
|
||||
*.gz binary
|
||||
*.odt binary
|
||||
*.ods binary
|
||||
*.odp binary
|
||||
*.ttf binary
|
||||
*.zim binary
|
||||
*.zim* binary
|
||||
*.eot binary
|
||||
*.svg binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.deb binary
|
||||
*.wasm binary
|
@ -165,7 +165,12 @@
|
||||
<Content Include="www\js\lib\arrayFromPolyfill.js" />
|
||||
<Content Include="www\js\lib\promisePolyfill.js" />
|
||||
<Content Include="www\js\lib\webpHeroBundle_0.0.0-dev.27.js" />
|
||||
<Content Include="www\js\lib\zstddec.js" />
|
||||
<Content Include="www\js\lib\xzdec-asm.js" />
|
||||
<Content Include="www\js\lib\xzdec-wasm.js" />
|
||||
<Content Include="www\js\lib\xzdec-wasm.wasm" />
|
||||
<Content Include="www\js\lib\zstddec-asm.js" />
|
||||
<Content Include="www\js\lib\zstddec-wasm.js" />
|
||||
<Content Include="www\js\lib\zstddec-wasm.wasm" />
|
||||
<Content Include="www\js\lib\zstddec_wrapper.js" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
<None Include="kiwix.pfx" />
|
||||
@ -316,7 +321,6 @@
|
||||
<Content Include="www\js\lib\uiUtil.js" />
|
||||
<Content Include="www\js\lib\utf8.js" />
|
||||
<Content Include="www\js\lib\util.js" />
|
||||
<Content Include="www\js\lib\xzdec.js" />
|
||||
<Content Include="www\js\lib\xzdec_wrapper.js" />
|
||||
<Content Include="www\js\lib\zimArchive.js" />
|
||||
<Content Include="www\js\lib\zimArchiveLoader.js" />
|
||||
|
@ -15,7 +15,7 @@ const regexpKiwixDownloadLinks = /download\.kiwix\.org/i;
|
||||
const regexpZIMUrlWithNamespace = /(?:^|\/)([^\/]+\/)([-ABCIJMUVWX])\/(.+)/;
|
||||
|
||||
const CACHE = "kiwix-precache-" + appVersion;
|
||||
const precacheFiles = [
|
||||
let precacheFiles = [
|
||||
".",
|
||||
"www",
|
||||
"www/",
|
||||
@ -68,16 +68,14 @@ const precacheFiles = [
|
||||
"www/js/lib/images.js",
|
||||
"www/js/lib/jquery-3.2.1.slim.js",
|
||||
"www/js/lib/kiwixServe.js",
|
||||
"www/js/lib/promisPolyfill.js",
|
||||
"www/js/lib/promisePolyfill.js",
|
||||
"www/js/lib/require.js",
|
||||
"www/js/lib/settingsStore.js",
|
||||
"www/js/lib/transformStyles.js",
|
||||
"www/js/lib/uiUtil.js",
|
||||
"www/js/lib/utf8.js",
|
||||
"www/js/lib/util.js",
|
||||
"www/js/lib/xzdec.js",
|
||||
"www/js/lib/xzdec_wrapper.js",
|
||||
"www/js/lib/zstddec.js",
|
||||
"www/js/lib/zstddec_wrapper.js",
|
||||
"www/js/lib/zimArchive.js",
|
||||
"www/js/lib/zimArchiveLoader.js",
|
||||
@ -95,6 +93,20 @@ const precacheFiles = [
|
||||
"www/js/katex/fonts/KaTeX_Size4-Regular.woff2"
|
||||
];
|
||||
|
||||
if ('WebAssembly' in self) {
|
||||
precacheFiles.push(
|
||||
"www/js/lib/xzdec-wasm.js",
|
||||
"www/js/lib/xzdec-wasm.wasm",
|
||||
"www/js/lib/zstddec-wasm.js",
|
||||
"www/js/lib/zstddec-wasm.wasm"
|
||||
);
|
||||
} else {
|
||||
precacheFiles.push(
|
||||
"www/js/lib/xzdec-asm.js",
|
||||
"www/js/lib/zstddec-asm.js"
|
||||
);
|
||||
}
|
||||
|
||||
// DEV: add any URL schemata that should be excluded from caching with the Cache API to the regex below
|
||||
// As of 08-2019 the chrome-extension: schema is incompatible with the Cache API
|
||||
// 'example-extension' is included to show how to add another schema if necessary
|
||||
|
@ -433,13 +433,21 @@ require.config({
|
||||
}
|
||||
});
|
||||
|
||||
requirejs(['bootstrap', 'promisePolyfill', 'arrayFromPolyfill'], function () {
|
||||
var req = ['bootstrap']; // Baseline Require array
|
||||
|
||||
// Add polyfills to the Require array only if needed
|
||||
if (!('Promise' in self)) req.push('promisePolyfill');
|
||||
if (!('from' in Array)) req.push('arrayFromPolyfill');
|
||||
|
||||
requirejs(req, function () {
|
||||
requirejs(['../app']);
|
||||
});
|
||||
|
||||
// Load the WebP Polyfills only if needed
|
||||
// Test if WebP is natively supported, and if not, set webpMachine to true. The value of webpMachine
|
||||
// will determine whether the WebP Polyfills will be loaded (currently only used in uiUtil.js)
|
||||
var webpMachine = false;
|
||||
// Using self-invoking function to avoid defining global functions and variables
|
||||
|
||||
// We use a self-invoking function here to avoid defining unnecessary global functions and variables
|
||||
(function (callback) {
|
||||
// Tests for native WebP support
|
||||
var webP = new Image();
|
||||
@ -451,4 +459,4 @@ var webpMachine = false;
|
||||
if (!support) {
|
||||
webpMachine = true;
|
||||
}
|
||||
});
|
||||
});
|
@ -1,105 +1,107 @@
|
||||
/**
|
||||
* abstractFilesystemAccess.js: Abstraction layer for file access.
|
||||
* This is currently only implemented for FirefoxOS, but could be extended to
|
||||
* Cordova, Electron or other ways to directly browse and read files from the
|
||||
* filesystem.
|
||||
* It is unfortunately not possible to do that inside a standard browser
|
||||
* (even inside an extension).
|
||||
*
|
||||
* Copyright 2014 Kiwix developers
|
||||
* License GPL v3:
|
||||
*
|
||||
* This file is part of Kiwix.
|
||||
*
|
||||
* Kiwix is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Kiwix is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
define(['jquery'], function(jQuery) {
|
||||
|
||||
/**
|
||||
* Storage implemented by Firefox OS
|
||||
*
|
||||
* @typedef StorageFirefoxOS
|
||||
* @property {DeviceStorage} _storage DeviceStorage
|
||||
* @property {String} storageName Name of the storage
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an abstraction layer around the FirefoxOS storage.
|
||||
* @param storage FirefoxOS DeviceStorage object
|
||||
*/
|
||||
function StorageFirefoxOS(storage) {
|
||||
this._storage = storage;
|
||||
this.storageName = storage.storageName;
|
||||
};
|
||||
/**
|
||||
* Access the given file.
|
||||
* @param {String} path absolute path to the file
|
||||
* @return {Promise} Promise which is resolved with a HTML5 file object and
|
||||
* rejected with an error message.
|
||||
*/
|
||||
StorageFirefoxOS.prototype.get = function(path) {
|
||||
var deferred = q.defer();
|
||||
var request = this._storage.get(path);
|
||||
request.onsuccess = function() { deferred.resolve(this.result); };
|
||||
request.onerror = function() { deferred.reject(this.error.name); };
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// We try to match both a standalone ZIM file (.zim) or
|
||||
// the first file of a split ZIM files collection (.zimaa)
|
||||
var regexpZIMFileName = /\.zim(aa)?$/i;
|
||||
|
||||
/**
|
||||
* Searches for archive files or directories.
|
||||
* @return {Promise} Promise which is resolved with an array of
|
||||
* paths and rejected with an error message.
|
||||
*/
|
||||
StorageFirefoxOS.prototype.scanForArchives = function() {
|
||||
var deferred = jQuery.Deferred();
|
||||
var directories = [];
|
||||
var cursor = this._storage.enumerate();
|
||||
cursor.onerror = function() {
|
||||
deferred.reject(cursor.error);
|
||||
};
|
||||
cursor.onsuccess = function() {
|
||||
if (!cursor.result) {
|
||||
deferred.resolve(directories);
|
||||
return;
|
||||
}
|
||||
var file = cursor.result;
|
||||
|
||||
if (regexpZIMFileName.test(file.name)) {
|
||||
directories.push(file.name);
|
||||
}
|
||||
|
||||
cursor.continue();
|
||||
};
|
||||
return deferred.promise();
|
||||
};
|
||||
|
||||
/**
|
||||
* Browse a path through DeviceStorage API
|
||||
* @param path Path where to look for files
|
||||
* @return {DOMCursor} Cursor of files found in given path
|
||||
*/
|
||||
StorageFirefoxOS.prototype.enumerate = function(path) {
|
||||
return this._storage.enumerate();
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
StorageFirefoxOS: StorageFirefoxOS
|
||||
};
|
||||
});
|
||||
/**
|
||||
* abstractFilesystemAccess.js: Abstraction layer for file access.
|
||||
* This is currently only implemented for FirefoxOS, but could be extended to
|
||||
* Cordova, Electron or other ways to directly browse and read files from the
|
||||
* filesystem.
|
||||
* It is unfortunately not possible to do that inside a standard browser
|
||||
* (even inside an extension).
|
||||
*
|
||||
* Copyright 2014 Kiwix developers
|
||||
* License GPL v3:
|
||||
*
|
||||
* This file is part of Kiwix.
|
||||
*
|
||||
* Kiwix is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Kiwix is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
define([], function() {
|
||||
|
||||
/**
|
||||
* Storage implemented by Firefox OS
|
||||
*
|
||||
* @typedef StorageFirefoxOS
|
||||
* @property {DeviceStorage} _storage DeviceStorage
|
||||
* @property {String} storageName Name of the storage
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an abstraction layer around the FirefoxOS storage.
|
||||
* @param storage FirefoxOS DeviceStorage object
|
||||
*/
|
||||
function StorageFirefoxOS(storage) {
|
||||
this._storage = storage;
|
||||
this.storageName = storage.storageName;
|
||||
};
|
||||
/**
|
||||
* Access the given file.
|
||||
* @param {String} path absolute path to the file
|
||||
* @return {Promise} Promise which is resolved with a HTML5 file object and
|
||||
* rejected with an error message.
|
||||
*/
|
||||
StorageFirefoxOS.prototype.get = function(path) {
|
||||
var that = this;
|
||||
return new Promise(function (resolve, reject){
|
||||
var request = that._storage.get(path);
|
||||
request.onsuccess = function() { resolve(this.result); };
|
||||
request.onerror = function() { reject(this.error.name); };
|
||||
});
|
||||
};
|
||||
|
||||
// We try to match both a standalone ZIM file (.zim) or
|
||||
// the first file of a split ZIM files collection (.zimaa)
|
||||
var regexpZIMFileName = /\.zim(aa)?$/i;
|
||||
|
||||
/**
|
||||
* Searches for archive files or directories.
|
||||
* @return {Promise} Promise which is resolved with an array of
|
||||
* paths and rejected with an error message.
|
||||
*/
|
||||
StorageFirefoxOS.prototype.scanForArchives = function() {
|
||||
var that = this;
|
||||
return new Promise(function (resolve, reject){
|
||||
var directories = [];
|
||||
var cursor = that._storage.enumerate();
|
||||
cursor.onerror = function() {
|
||||
reject(cursor.error);
|
||||
};
|
||||
cursor.onsuccess = function() {
|
||||
if (!cursor.result) {
|
||||
resolve(directories);
|
||||
return;
|
||||
}
|
||||
var file = cursor.result;
|
||||
|
||||
if (regexpZIMFileName.test(file.name)) {
|
||||
directories.push(file.name);
|
||||
}
|
||||
|
||||
cursor.continue();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Browse a path through DeviceStorage API
|
||||
* @param path Path where to look for files
|
||||
* @return {DOMCursor} Cursor of files found in given path
|
||||
*/
|
||||
StorageFirefoxOS.prototype.enumerate = function(path) {
|
||||
return this._storage.enumerate();
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
StorageFirefoxOS: StorageFirefoxOS
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,7 @@
|
||||
/**
|
||||
* Simple Array.from polyfi8ll (with Set support) from https://stackoverflow.com/a/62682524/9727685
|
||||
* Simple Array.from polyfill (with Set support) from https://stackoverflow.com/a/62682524/9727685
|
||||
*/
|
||||
(function () {
|
||||
// Detection
|
||||
if ('from' in Array) return;
|
||||
|
||||
function arrayFrom(arr, callbackFn, thisArg) {
|
||||
//if you need you can uncomment the following line
|
||||
@ -31,5 +29,5 @@
|
||||
return arNew;
|
||||
}
|
||||
//You could also use it without the following line, but it is not recommended because native function is faster.
|
||||
Array.from = arrayFrom; //We set it as polyfill
|
||||
Array.from = Array.from || arrayFrom; //We set it as polyfill
|
||||
}());
|
@ -1,186 +1,186 @@
|
||||
/**
|
||||
* filecache.js: Generic cache for small, frequently read file slices.
|
||||
* It discards cached blocks according to a least-recently-used algorithm.
|
||||
* It is used primarily for fast Directory Entry lookup, speeding up binary search.
|
||||
*
|
||||
* Copyright 2020 Mossroy, peter-x, jaifroid and contributors
|
||||
* License GPL v3:
|
||||
*
|
||||
* This file is part of Kiwix.
|
||||
*
|
||||
* Kiwix JS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Kiwix JS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kiwix JS (file LICENSE). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
define([], function() {
|
||||
/**
|
||||
* Set maximum number of cache blocks of BLOCK_SIZE bytes each
|
||||
* Maximum size of cache in bytes = MAX_CACHE_SIZE * BLOCK_SIZE
|
||||
* @constant
|
||||
* @type {Number}
|
||||
*/
|
||||
const MAX_CACHE_SIZE = 4000;
|
||||
|
||||
/**
|
||||
* The maximum blocksize to read or store via the block cache (bytes)
|
||||
* @constant
|
||||
* @type {Number}
|
||||
*/
|
||||
const BLOCK_SIZE = 4096;
|
||||
|
||||
/**
|
||||
* A Block Cache employing a Least Recently Used caching strategy
|
||||
* @typedef {Object} BlockCache
|
||||
* @property {Number} capacity The maximum number of entries in the cache
|
||||
* @property {Map} cache A map to store the cache keys and data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new cache with max size limit of MAX_CACHE_SIZE blocks
|
||||
* LRUCache implemnentation with Map adapted from https://markmurray.co/blog/lru-cache/
|
||||
*/
|
||||
function LRUCache() {
|
||||
/** CACHE TUNING **/
|
||||
// console.log('Creating cache of size ' + MAX_CACHE_SIZE + ' * ' + BLOCK_SIZE + ' bytes');
|
||||
// Initialize persistent Cache properties
|
||||
this.capacity = MAX_CACHE_SIZE;
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to retrieve an element by its id. If it is not present in the cache, returns undefined; if it is present,
|
||||
* then the value is returned and the entry is moved to the bottom of the cache
|
||||
* @param {String} key The block cache entry key (file.id + ':' + byte offset)
|
||||
* @returns {Uint8Array|undefined} The requested cache data or undefined
|
||||
*/
|
||||
LRUCache.prototype.get = function (key) {
|
||||
var entry = this.cache.get(key);
|
||||
// If the key does not exist, return
|
||||
if (!entry) return entry;
|
||||
// Remove the key and re-insert it (this moves the key to the bottom of the Map: bottom = most recent)
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, entry);
|
||||
// Return the cached data
|
||||
return entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores a value in the cache by id and prunes the least recently used entry if the cache is larger than MAX_CACHE_SIZE
|
||||
* @param {String} key The key under which to store the value (file.id + ':' + byte offset from start of ZIM archive)
|
||||
* @param {Uint8Array} value The value to store in the cache
|
||||
*/
|
||||
LRUCache.prototype.store = function (key, value) {
|
||||
// We get the existing entry's object for memory-management purposes; if it exists, it will contain identical data
|
||||
// to <value>, but <entry> is strongly referenced by the Map. (It should be rare that two async Promises attempt to
|
||||
// store the same data in the Cache, once the Cache is sufficiently populated.)
|
||||
var entry = this.cache.get(key);
|
||||
// If the key already exists, delete it and re-insert it, so that it will be added
|
||||
// to the bottom of the Map (bottom = most recent)
|
||||
if (entry) this.cache.delete(key);
|
||||
else entry = value;
|
||||
this.cache.set(key, entry);
|
||||
// If we've exceeded the cache capacity, then delete the least recently accessed value,
|
||||
// which will be the item at the top of the Map, i.e the first position
|
||||
if (this.cache.size > this.capacity) {
|
||||
if (this.cache.keys) {
|
||||
var firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
} else {
|
||||
// IE11 doesn't support the keys iterator, so we have to do a forEach loop through all 4000 entries
|
||||
// to get the oldest values. To prevent excessive iterations, we delete 25% at a time.
|
||||
var q = Math.floor(0.25 * this.capacity);
|
||||
var c = 0;
|
||||
// console.log('Deleteing ' + q + ' cache entries');
|
||||
this.cache.forEach(function (v, k, map) {
|
||||
if (c > q) return;
|
||||
map.delete(k);
|
||||
c++;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A new Block Cache
|
||||
* @type {BlockCache}
|
||||
*/
|
||||
var cache = new LRUCache();
|
||||
|
||||
/** CACHE TUNING **/
|
||||
|
||||
// DEV: Uncomment this block and blocks below marked 'CACHE TUNING' to measure Cache hit and miss rates for different Cache sizes
|
||||
// var hits = 0;
|
||||
// var misses = 0;
|
||||
|
||||
/**
|
||||
* Read a certain byte range in the given file, breaking the range into chunks that go through the cache
|
||||
* If a read of more than BLOCK_SIZE * 2 (bytes) is requested, do not use the cache
|
||||
* @param {Object} file The requested ZIM archive to read from
|
||||
* @param {Number} begin The byte from which to start reading
|
||||
* @param {Number} end The byte at which to stop reading (end will not be read)
|
||||
* @return {Promise<Uint8Array>} A Promise that resolves to the correctly concatenated data from the cache
|
||||
* or from the ZIM archive
|
||||
*/
|
||||
var read = function(file, begin, end) {
|
||||
// Read large chunks bypassing the block cache because we would have to
|
||||
// stitch together too many blocks and would clog the cache
|
||||
if (end - begin > BLOCK_SIZE * 2) return file._readSplitSlice(begin, end);
|
||||
var readRequests = [];
|
||||
var blocks = {};
|
||||
// Look for the requested data in the blocks: we may need to stitch together data from two or more blocks
|
||||
for (var id = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; id < end; id += BLOCK_SIZE) {
|
||||
var block = cache.get(file.id + ':' + id);
|
||||
if (block === undefined) {
|
||||
// Data not in cache, so read from archive
|
||||
/** CACHE TUNING **/
|
||||
// misses++;
|
||||
// DEV: This is a self-calling function, i.e. the function is called with an argument of <id> which then
|
||||
// becomes the <offset> parameter
|
||||
readRequests.push(function(offset) {
|
||||
return file._readSplitSlice(offset, offset + BLOCK_SIZE).then(function(result) {
|
||||
cache.store(file.id + ':' + offset, result);
|
||||
blocks[offset] = result;
|
||||
});
|
||||
}(id));
|
||||
} else {
|
||||
/** CACHE TUNING **/
|
||||
// hits++;
|
||||
blocks[id] = block;
|
||||
}
|
||||
}
|
||||
/** CACHE TUNING **/
|
||||
// if (misses + hits > 2000) {
|
||||
// console.log('** Block cache hit rate: ' + Math.round(hits / (hits + misses) * 1000) / 10 + '% [ hits:' + hits +
|
||||
// ' / misses:' + misses + ' ] Size: ' + cache.cache.size);
|
||||
// hits = 0;
|
||||
// misses = 0;
|
||||
// }
|
||||
// Wait for all the blocks to be read either from the cache or from the archive
|
||||
return Promise.all(readRequests).then(function() {
|
||||
var result = new Uint8Array(end - begin);
|
||||
var pos = 0;
|
||||
// Stitch together the data parts in the right order
|
||||
for (var i = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; i < end; i += BLOCK_SIZE) {
|
||||
var b = Math.max(i, begin) - i;
|
||||
var e = Math.min(end, i + BLOCK_SIZE) - i;
|
||||
if (blocks[i].subarray) result.set(blocks[i].subarray(b, e), pos);
|
||||
pos += e - b;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
read: read
|
||||
};
|
||||
});
|
||||
/**
|
||||
* filecache.js: Generic cache for small, frequently read file slices.
|
||||
* It discards cached blocks according to a least-recently-used algorithm.
|
||||
* It is used primarily for fast Directory Entry lookup, speeding up binary search.
|
||||
*
|
||||
* Copyright 2020 Mossroy, peter-x, jaifroid and contributors
|
||||
* License GPL v3:
|
||||
*
|
||||
* This file is part of Kiwix.
|
||||
*
|
||||
* Kiwix JS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Kiwix JS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kiwix JS (file LICENSE). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
define([], function() {
|
||||
/**
|
||||
* Set maximum number of cache blocks of BLOCK_SIZE bytes each
|
||||
* Maximum size of cache in bytes = MAX_CACHE_SIZE * BLOCK_SIZE
|
||||
* @constant
|
||||
* @type {Number}
|
||||
*/
|
||||
const MAX_CACHE_SIZE = 4000;
|
||||
|
||||
/**
|
||||
* The maximum blocksize to read or store via the block cache (bytes)
|
||||
* @constant
|
||||
* @type {Number}
|
||||
*/
|
||||
const BLOCK_SIZE = 4096;
|
||||
|
||||
/**
|
||||
* A Block Cache employing a Least Recently Used caching strategy
|
||||
* @typedef {Object} BlockCache
|
||||
* @property {Number} capacity The maximum number of entries in the cache
|
||||
* @property {Map} cache A map to store the cache keys and data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new cache with max size limit of MAX_CACHE_SIZE blocks
|
||||
* LRUCache implemnentation with Map adapted from https://markmurray.co/blog/lru-cache/
|
||||
*/
|
||||
function LRUCache() {
|
||||
/** CACHE TUNING **/
|
||||
// console.log('Creating cache of size ' + MAX_CACHE_SIZE + ' * ' + BLOCK_SIZE + ' bytes');
|
||||
// Initialize persistent Cache properties
|
||||
this.capacity = MAX_CACHE_SIZE;
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to retrieve an element by its id. If it is not present in the cache, returns undefined; if it is present,
|
||||
* then the value is returned and the entry is moved to the bottom of the cache
|
||||
* @param {String} key The block cache entry key (file.id + ':' + byte offset)
|
||||
* @returns {Uint8Array|undefined} The requested cache data or undefined
|
||||
*/
|
||||
LRUCache.prototype.get = function (key) {
|
||||
var entry = this.cache.get(key);
|
||||
// If the key does not exist, return
|
||||
if (!entry) return entry;
|
||||
// Remove the key and re-insert it (this moves the key to the bottom of the Map: bottom = most recent)
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, entry);
|
||||
// Return the cached data
|
||||
return entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores a value in the cache by id and prunes the least recently used entry if the cache is larger than MAX_CACHE_SIZE
|
||||
* @param {String} key The key under which to store the value (file.id + ':' + byte offset from start of ZIM archive)
|
||||
* @param {Uint8Array} value The value to store in the cache
|
||||
*/
|
||||
LRUCache.prototype.store = function (key, value) {
|
||||
// We get the existing entry's object for memory-management purposes; if it exists, it will contain identical data
|
||||
// to <value>, but <entry> is strongly referenced by the Map. (It should be rare that two async Promises attempt to
|
||||
// store the same data in the Cache, once the Cache is sufficiently populated.)
|
||||
var entry = this.cache.get(key);
|
||||
// If the key already exists, delete it and re-insert it, so that it will be added
|
||||
// to the bottom of the Map (bottom = most recent)
|
||||
if (entry) this.cache.delete(key);
|
||||
else entry = value;
|
||||
this.cache.set(key, entry);
|
||||
// If we've exceeded the cache capacity, then delete the least recently accessed value,
|
||||
// which will be the item at the top of the Map, i.e the first position
|
||||
if (this.cache.size > this.capacity) {
|
||||
if (this.cache.keys) {
|
||||
var firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
} else {
|
||||
// IE11 doesn't support the keys iterator, so we have to do a forEach loop through all 4000 entries
|
||||
// to get the oldest values. To prevent excessive iterations, we delete 25% at a time.
|
||||
var q = Math.floor(0.25 * this.capacity);
|
||||
var c = 0;
|
||||
// console.log('Deleteing ' + q + ' cache entries');
|
||||
this.cache.forEach(function (v, k, map) {
|
||||
if (c > q) return;
|
||||
map.delete(k);
|
||||
c++;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A new Block Cache
|
||||
* @type {BlockCache}
|
||||
*/
|
||||
var cache = new LRUCache();
|
||||
|
||||
/** CACHE TUNING **/
|
||||
// DEV: Uncomment this block and blocks below marked 'CACHE TUNING' to measure Cache hit and miss rates for different Cache sizes
|
||||
// var hits = 0;
|
||||
// var misses = 0;
|
||||
|
||||
/**
|
||||
* Read a certain byte range in the given file, breaking the range into chunks that go through the cache
|
||||
* If a read of more than BLOCK_SIZE * 2 (bytes) is requested, do not use the cache
|
||||
* @param {Object} file The requested ZIM archive to read from
|
||||
* @param {Number} begin The byte from which to start reading
|
||||
* @param {Number} end The byte at which to stop reading (end will not be read)
|
||||
* @return {Promise<Uint8Array>} A Promise that resolves to the correctly concatenated data from the cache
|
||||
* or from the ZIM archive
|
||||
*/
|
||||
var read = function(file, begin, end) {
|
||||
// Read large chunks bypassing the block cache because we would have to
|
||||
// stitch together too many blocks and would clog the cache
|
||||
if (end - begin > BLOCK_SIZE * 2) return file._readSplitSlice(begin, end);
|
||||
var readRequests = [];
|
||||
var blocks = {};
|
||||
// Look for the requested data in the blocks: we may need to stitch together data from two or more blocks
|
||||
for (var id = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; id < end; id += BLOCK_SIZE) {
|
||||
var block = cache.get(file.id + ':' + id);
|
||||
if (block === undefined) {
|
||||
// Data not in cache, so read from archive
|
||||
/** CACHE TUNING **/
|
||||
// misses++;
|
||||
// DEV: This is a self-calling function, i.e. the function is called with an argument of <id> which then
|
||||
// becomes the <offset> parameter
|
||||
readRequests.push(function(offset) {
|
||||
return file._readSplitSlice(offset, offset + BLOCK_SIZE).then(function(result) {
|
||||
cache.store(file.id + ':' + offset, result);
|
||||
blocks[offset] = result;
|
||||
});
|
||||
}(id));
|
||||
} else {
|
||||
/** CACHE TUNING **/
|
||||
// hits++;
|
||||
blocks[id] = block;
|
||||
}
|
||||
}
|
||||
/** CACHE TUNING **/
|
||||
// if (misses + hits > 2000) {
|
||||
// console.log('** Block cache hit rate: ' + Math.round(hits / (hits + misses) * 1000) / 10 + '% [ hits:' + hits +
|
||||
// ' / misses:' + misses + ' ] Size: ' + cache.cache.size);
|
||||
// hits = 0;
|
||||
// misses = 0;
|
||||
// }
|
||||
// Wait for all the blocks to be read either from the cache or from the archive
|
||||
return Promise.all(readRequests).then(function() {
|
||||
var result = new Uint8Array(end - begin);
|
||||
var pos = 0;
|
||||
// Stitch together the data parts in the right order
|
||||
for (var i = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; i < end; i += BLOCK_SIZE) {
|
||||
var b = Math.max(i, begin) - i;
|
||||
var e = Math.min(end, i + BLOCK_SIZE) - i;
|
||||
if (blocks[i].subarray) result.set(blocks[i].subarray(b, e), pos);
|
||||
pos += e - b;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
read: read
|
||||
};
|
||||
});
|
||||
|
@ -12,8 +12,6 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if ('Promise' in self) return;
|
||||
|
||||
var $undefined
|
||||
, $null = null
|
||||
, isBrowser = typeof self === 'object'
|
||||
|
54
www/js/lib/xzdec-asm.js
Normal file
54
www/js/lib/xzdec-asm.js
Normal file
File diff suppressed because one or more lines are too long
32
www/js/lib/xzdec-wasm.js
Normal file
32
www/js/lib/xzdec-wasm.js
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
var XZ = (function () {
|
||||
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
|
||||
|
||||
return (
|
||||
function(XZ) {
|
||||
XZ = XZ || {};
|
||||
|
||||
|
||||
var a;a||(a=typeof XZ !== 'undefined' ? XZ : {});var e,f;a.ready=new Promise(function(b,c){e=b;f=c});var h={},k;for(k in a)a.hasOwnProperty(k)&&(h[k]=a[k]);var m="";"undefined"!==typeof document&&document.currentScript&&(m=document.currentScript.src);_scriptDir&&(m=_scriptDir);0!==m.indexOf("blob:")?m=m.substr(0,m.lastIndexOf("/")+1):m="";var n=a.printErr||console.warn.bind(console);for(k in h)h.hasOwnProperty(k)&&(a[k]=h[k]);h=null;var p;a.wasmBinary&&(p=a.wasmBinary);
|
||||
var noExitRuntime=a.noExitRuntime||!0;"object"!==typeof WebAssembly&&q("no native wasm support detected");var r,t=!1,u,v,w=[],x=[],z=[];function A(){var b=a.preRun.shift();w.unshift(b)}var B=0,C=null,D=null;a.preloadedImages={};a.preloadedAudios={};function q(b){if(a.onAbort)a.onAbort(b);n(b);t=!0;b=new WebAssembly.RuntimeError("abort("+b+"). Build with -s ASSERTIONS=1 for more info.");f(b);throw b;}function E(){return F.startsWith("data:application/octet-stream;base64,")}var F;F="xzdec-wasm.wasm";
|
||||
if(!E()){var G=F;F=a.locateFile?a.locateFile(G,m):m+G}function H(){var b=F;try{if(b==F&&p)return new Uint8Array(p);throw"both async and sync fetching of the wasm failed";}catch(c){q(c)}}function I(){return p||"function"!==typeof fetch?Promise.resolve().then(function(){return H()}):fetch(F,{credentials:"same-origin"}).then(function(b){if(!b.ok)throw"failed to load wasm binary file at '"+F+"'";return b.arrayBuffer()}).catch(function(){return H()})}
|
||||
function J(b){for(;0<b.length;){var c=b.shift();if("function"==typeof c)c(a);else{var g=c.u;"number"===typeof g?void 0===c.s?v.get(g)():v.get(g)(c.s):g(void 0===c.s?null:c.s)}}}var K={a:function(b,c,g){u.copyWithin(b,c,c+g)},b:function(){q("OOM")}};
|
||||
(function(){function b(d){a.asm=d.exports;r=a.asm.c;d=r.buffer;a.HEAP8=new Int8Array(d);a.HEAP16=new Int16Array(d);a.HEAP32=new Int32Array(d);a.HEAPU8=u=new Uint8Array(d);a.HEAPU16=new Uint16Array(d);a.HEAPU32=new Uint32Array(d);a.HEAPF32=new Float32Array(d);a.HEAPF64=new Float64Array(d);v=a.asm.o;x.unshift(a.asm.d);B--;a.monitorRunDependencies&&a.monitorRunDependencies(B);0==B&&(null!==C&&(clearInterval(C),C=null),D&&(d=D,D=null,d()))}function c(d){b(d.instance)}function g(d){return I().then(function(l){return WebAssembly.instantiate(l,
|
||||
y)}).then(d,function(l){n("failed to asynchronously prepare wasm: "+l);q(l)})}var y={a:K};B++;a.monitorRunDependencies&&a.monitorRunDependencies(B);if(a.instantiateWasm)try{return a.instantiateWasm(y,b)}catch(d){return n("Module.instantiateWasm callback failed with error: "+d),!1}(function(){return p||"function"!==typeof WebAssembly.instantiateStreaming||E()||"function"!==typeof fetch?g(c):fetch(F,{credentials:"same-origin"}).then(function(d){return WebAssembly.instantiateStreaming(d,y).then(c,function(l){n("wasm streaming compile failed: "+
|
||||
l);n("falling back to ArrayBuffer instantiation");return g(c)})})})().catch(f);return{}})();a.___wasm_call_ctors=function(){return(a.___wasm_call_ctors=a.asm.d).apply(null,arguments)};a._init=function(){return(a._init=a.asm.e).apply(null,arguments)};a._init_decompression=function(){return(a._init_decompression=a.asm.f).apply(null,arguments)};a._input_empty=function(){return(a._input_empty=a.asm.g).apply(null,arguments)};a._get_in_buffer=function(){return(a._get_in_buffer=a.asm.h).apply(null,arguments)};
|
||||
a._set_new_input=function(){return(a._set_new_input=a.asm.i).apply(null,arguments)};a._decompress=function(){return(a._decompress=a.asm.j).apply(null,arguments)};a._get_out_pos=function(){return(a._get_out_pos=a.asm.k).apply(null,arguments)};a._get_out_buffer=function(){return(a._get_out_buffer=a.asm.l).apply(null,arguments)};a._out_buffer_cleared=function(){return(a._out_buffer_cleared=a.asm.m).apply(null,arguments)};a._release=function(){return(a._release=a.asm.n).apply(null,arguments)};var L;
|
||||
D=function M(){L||N();L||(D=M)};
|
||||
function N(){function b(){if(!L&&(L=!0,a.calledRun=!0,!t)){J(x);e(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;){var c=a.postRun.shift();z.unshift(c)}J(z)}}if(!(0<B)){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)A();J(w);0<B||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);b()},1)):b())}}a.run=N;
|
||||
if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();N();
|
||||
|
||||
|
||||
return XZ.ready
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = XZ;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return XZ; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["XZ"] = XZ;
|
BIN
www/js/lib/xzdec-wasm.wasm
Normal file
BIN
www/js/lib/xzdec-wasm.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* xzdec_wrapper.js: Javascript wrapper around compiled xz decompressor.
|
||||
*
|
||||
* Copyright 2015 Mossroy and contributors
|
||||
* Copyright 2021 Mossroy and contributors
|
||||
* License GPL v3:
|
||||
*
|
||||
* This file is part of Kiwix.
|
||||
@ -20,10 +20,55 @@
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
define(['xzdec'], function() {
|
||||
// DEV: xzdec.js emits a global Module variable, which cannot be set in requireJS function line above, though it can be loaded in definition
|
||||
var xzdec = Module;
|
||||
xzdec._init();
|
||||
|
||||
// DEV: Put your RequireJS definition in the rqDefXZ array below, and any function exports in the function parenthesis of the define statement
|
||||
// We need to do it this way in order to load the wasm or asm versions of xzdec conditionally. Older browsers can only use the asm version
|
||||
// because they cannot interpret WebAssembly.
|
||||
var rqDefXZ = [];
|
||||
|
||||
// Select asm or wasm conditionally
|
||||
if ('WebAssembly' in self) {
|
||||
console.debug('Using WASM xz decoder');
|
||||
rqDefXZ.push('xzdec-wasm');
|
||||
} else {
|
||||
console.debug('Using ASM xz decoder');
|
||||
rqDefXZ.push('xzdec-asm');
|
||||
}
|
||||
|
||||
define(rqDefXZ, function() {
|
||||
// DEV: xzdec.js has been compiled with `-s EXPORT_NAME="XZ" -s MODULARIZE=1` to avoid a clash with zstddec.js
|
||||
// Note that we include xzdec-asm or xzdec-wasm above in requireJS definition, but we cannot change the name in the function list
|
||||
// There is no longer any need to load it in index.html
|
||||
// For explanation of loading method below to avoid conflicts, see https://github.com/emscripten-core/emscripten/blob/master/src/settings.js
|
||||
|
||||
/**
|
||||
* @typedef EMSInstance An object type representing an Emscripten instance
|
||||
*/
|
||||
|
||||
/**
|
||||
* The XZ Decoder instance
|
||||
* @type EMSInstance
|
||||
*/
|
||||
var xzdec;
|
||||
|
||||
var instantiateDecoder = function (instance) {
|
||||
xzdec = instance;
|
||||
};
|
||||
|
||||
XZ().then(instantiateDecoder)
|
||||
.catch(function (err) {
|
||||
console.debug(err);
|
||||
if (/CompileError.+?WASM/i.test(err.message)) {
|
||||
console.log("WASM failed to load, falling back to ASM...", err);
|
||||
XZ = null;
|
||||
require(['xzdec-asm'], function() {
|
||||
XZ().then(instantiateDecoder)
|
||||
.catch(function (err) {
|
||||
console.error('Could not instantiate any decoder!', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Number of milliseconds to wait for the decompressor to be available for another chunk
|
||||
@ -86,6 +131,7 @@ define(['xzdec'], function() {
|
||||
* @returns {Promise} A Promise for the read data
|
||||
*/
|
||||
Decompressor.prototype.readSliceSingleThread = function(offset, length) {
|
||||
// Tests whether the decompressor is ready (initiated) and not busy
|
||||
if (xzdec && !busy) {
|
||||
return this.readSlice(offset, length);
|
||||
} else {
|
||||
@ -94,7 +140,6 @@ define(['xzdec'], function() {
|
||||
// before using it for another decompression
|
||||
var that = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
setTimeout(function(){
|
||||
that.readSliceSingleThread(offset, length).then(resolve, reject);
|
||||
}, DELAY_WAITING_IDLE_DECOMPRESSOR);
|
||||
@ -149,7 +194,6 @@ define(['xzdec'], function() {
|
||||
*/
|
||||
Decompressor.prototype._fillInBufferIfNeeded = function() {
|
||||
if (!xzdec._input_empty(this._decHandle)) {
|
||||
// DEV: When converting to Promise/A+, use Promise.resolve(0) here
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
var that = this;
|
||||
|
@ -20,6 +20,21 @@
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Add Polyfill currently required by IE11 to run zstddec-asm and xzdec-asm
|
||||
* See https://github.com/emscripten-core/emscripten/issues/14700
|
||||
* If this is resolved upstream, remove this polyfill
|
||||
*/
|
||||
if (!String.prototype.startsWith) {
|
||||
Object.defineProperty(String.prototype, 'startsWith', {
|
||||
value: function(search, rawPos) {
|
||||
var pos = rawPos > 0 ? rawPos|0 : 0;
|
||||
return this.substring(pos, pos + search.length) === search;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'filecache'], function(xz, zstd, util, utf8, zimDirEntry, FileCache) {
|
||||
|
||||
/**
|
||||
|
58
www/js/lib/zstddec-asm.js
Normal file
58
www/js/lib/zstddec-asm.js
Normal file
File diff suppressed because one or more lines are too long
36
www/js/lib/zstddec-wasm.js
Normal file
36
www/js/lib/zstddec-wasm.js
Normal file
@ -0,0 +1,36 @@
|
||||
|
||||
var ZD = (function() {
|
||||
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
|
||||
|
||||
return (
|
||||
function(ZD) {
|
||||
ZD = ZD || {};
|
||||
|
||||
|
||||
var a;a||(a=typeof ZD !== 'undefined' ? ZD : {});var q,r;a.ready=new Promise(function(b,c){q=b;r=c});var t={},w;for(w in a)a.hasOwnProperty(w)&&(t[w]=a[w]);var x="";"undefined"!==typeof document&&document.currentScript&&(x=document.currentScript.src);_scriptDir&&(x=_scriptDir);0!==x.indexOf("blob:")?x=x.substr(0,x.lastIndexOf("/")+1):x="";var y=a.printErr||console.warn.bind(console);for(w in t)t.hasOwnProperty(w)&&(a[w]=t[w]);t=null;var z;a.wasmBinary&&(z=a.wasmBinary);
|
||||
var noExitRuntime=a.noExitRuntime||!0;"object"!==typeof WebAssembly&&A("no native wasm support detected");var B,C=!1;function D(b){var c=a["_"+b];c||A("Assertion failed: Cannot call unknown function "+(b+", make sure it is exported"));return c}
|
||||
function aa(b,c,g,n){var e={string:function(d){var m=0;if(null!==d&&void 0!==d&&0!==d){var l=(d.length<<2)+1;m=E(l);var h=m,f=F;if(0<l){l=h+l-1;for(var u=0;u<d.length;++u){var k=d.charCodeAt(u);if(55296<=k&&57343>=k){var ba=d.charCodeAt(++u);k=65536+((k&1023)<<10)|ba&1023}if(127>=k){if(h>=l)break;f[h++]=k}else{if(2047>=k){if(h+1>=l)break;f[h++]=192|k>>6}else{if(65535>=k){if(h+2>=l)break;f[h++]=224|k>>12}else{if(h+3>=l)break;f[h++]=240|k>>18;f[h++]=128|k>>12&63}f[h++]=128|k>>6&63}f[h++]=128|k&63}}f[h]=
|
||||
0}}return m},array:function(d){var m=E(d.length);H.set(d,m);return m}},p=D(b),G=[];b=0;if(n)for(var v=0;v<n.length;v++){var Q=e[g[v]];Q?(0===b&&(b=I()),G[v]=Q(n[v])):G[v]=n[v]}g=p.apply(null,G);g=function(d){if("string"===c)if(d){for(var m=F,l=d+NaN,h=d;m[h]&&!(h>=l);)++h;if(16<h-d&&m.subarray&&J)d=J.decode(m.subarray(d,h));else{for(l="";d<h;){var f=m[d++];if(f&128){var u=m[d++]&63;if(192==(f&224))l+=String.fromCharCode((f&31)<<6|u);else{var k=m[d++]&63;f=224==(f&240)?(f&15)<<12|u<<6|k:(f&7)<<18|
|
||||
u<<12|k<<6|m[d++]&63;65536>f?l+=String.fromCharCode(f):(f-=65536,l+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else l+=String.fromCharCode(f)}d=l}}else d="";else d="boolean"===c?!!d:d;return d}(g);0!==b&&K(b);return g}var J="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0,H,F,L,M=[],N=[],O=[];function ca(){var b=a.preRun.shift();M.unshift(b)}var P=0,R=null,S=null;a.preloadedImages={};a.preloadedAudios={};
|
||||
function A(b){if(a.onAbort)a.onAbort(b);y(b);C=!0;b=new WebAssembly.RuntimeError("abort("+b+"). Build with -s ASSERTIONS=1 for more info.");r(b);throw b;}function T(){return U.startsWith("data:application/octet-stream;base64,")}var U;U="zstddec-wasm.wasm";if(!T()){var V=U;U=a.locateFile?a.locateFile(V,x):x+V}function W(){var b=U;try{if(b==U&&z)return new Uint8Array(z);throw"both async and sync fetching of the wasm failed";}catch(c){A(c)}}
|
||||
function da(){return z||"function"!==typeof fetch?Promise.resolve().then(function(){return W()}):fetch(U,{credentials:"same-origin"}).then(function(b){if(!b.ok)throw"failed to load wasm binary file at '"+U+"'";return b.arrayBuffer()}).catch(function(){return W()})}function X(b){for(;0<b.length;){var c=b.shift();if("function"==typeof c)c(a);else{var g=c.u;"number"===typeof g?void 0===c.s?L.get(g)():L.get(g)(c.s):g(void 0===c.s?null:c.s)}}}var ea={a:function(b,c,g){F.copyWithin(b,c,c+g)},b:function(){A("OOM")}};
|
||||
(function(){function b(e){a.asm=e.exports;B=a.asm.c;e=B.buffer;a.HEAP8=H=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=new Int32Array(e);a.HEAPU8=F=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e);L=a.asm.o;N.unshift(a.asm.d);P--;a.monitorRunDependencies&&a.monitorRunDependencies(P);0==P&&(null!==R&&(clearInterval(R),R=null),S&&(e=S,S=null,e()))}function c(e){b(e.instance)}function g(e){return da().then(function(p){return WebAssembly.instantiate(p,
|
||||
n)}).then(e,function(p){y("failed to asynchronously prepare wasm: "+p);A(p)})}var n={a:ea};P++;a.monitorRunDependencies&&a.monitorRunDependencies(P);if(a.instantiateWasm)try{return a.instantiateWasm(n,b)}catch(e){return y("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return z||"function"!==typeof WebAssembly.instantiateStreaming||T()||"function"!==typeof fetch?g(c):fetch(U,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,n).then(c,function(p){y("wasm streaming compile failed: "+
|
||||
p);y("falling back to ArrayBuffer instantiation");return g(c)})})})().catch(r);return{}})();a.___wasm_call_ctors=function(){return(a.___wasm_call_ctors=a.asm.d).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.e).apply(null,arguments)};a._free=function(){return(a._free=a.asm.f).apply(null,arguments)};a._ZSTD_isError=function(){return(a._ZSTD_isError=a.asm.g).apply(null,arguments)};a._ZSTD_getErrorName=function(){return(a._ZSTD_getErrorName=a.asm.h).apply(null,arguments)};
|
||||
a._ZSTD_createDStream=function(){return(a._ZSTD_createDStream=a.asm.i).apply(null,arguments)};a._ZSTD_freeDStream=function(){return(a._ZSTD_freeDStream=a.asm.j).apply(null,arguments)};a._ZSTD_DStreamInSize=function(){return(a._ZSTD_DStreamInSize=a.asm.k).apply(null,arguments)};a._ZSTD_DStreamOutSize=function(){return(a._ZSTD_DStreamOutSize=a.asm.l).apply(null,arguments)};a._ZSTD_initDStream=function(){return(a._ZSTD_initDStream=a.asm.m).apply(null,arguments)};
|
||||
a._ZSTD_decompressStream=function(){return(a._ZSTD_decompressStream=a.asm.n).apply(null,arguments)};var I=a.stackSave=function(){return(I=a.stackSave=a.asm.p).apply(null,arguments)},K=a.stackRestore=function(){return(K=a.stackRestore=a.asm.q).apply(null,arguments)},E=a.stackAlloc=function(){return(E=a.stackAlloc=a.asm.r).apply(null,arguments)};a.cwrap=function(b,c,g,n){g=g||[];var e=g.every(function(p){return"number"===p});return"string"!==c&&e&&!n?D(b):function(){return aa(b,c,g,arguments)}};var Y;
|
||||
S=function fa(){Y||Z();Y||(S=fa)};
|
||||
function Z(){function b(){if(!Y&&(Y=!0,a.calledRun=!0,!C)){X(N);q(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;){var c=a.postRun.shift();O.unshift(c)}X(O)}}if(!(0<P)){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)ca();X(M);0<P||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);b()},1)):b())}}a.run=Z;
|
||||
if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();Z();
|
||||
|
||||
|
||||
return ZD.ready
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = ZD;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return ZD; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["ZD"] = ZD;
|
BIN
www/js/lib/zstddec-wasm.wasm
Normal file
BIN
www/js/lib/zstddec-wasm.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -20,10 +20,24 @@
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
'use strict';
|
||||
define(['zstddec'], function() {
|
||||
// DEV: zstddec.js has been compiled with `-s EXPORT_NAME="ZD" -s MODULARIZE=1` to avoid a clash with xzdec which uses "Module" as its exported object
|
||||
// Note that we include zstddec above in requireJS definition, but we cannot change the name in the function list
|
||||
// There is no longer any need to load it in index.html
|
||||
|
||||
// DEV: Put your RequireJS definition in the rqDefZD array below, and any function exports in the function parenthesis of the define statement
|
||||
// We need to do it this way in order to load the wasm or asm versions of zstddec conditionally. Older browsers can only use the asm version
|
||||
// because they cannot interpret WebAssembly.
|
||||
var rqDefZD = [];
|
||||
|
||||
// Select asm or wasm conditionally
|
||||
if ('WebAssembly' in self) {
|
||||
console.debug('Using WASM zstandard decoder');
|
||||
rqDefZD.push('zstddec-wasm');
|
||||
} else {
|
||||
console.debug('Using ASM zstandard decoder');
|
||||
rqDefZD.push('zstddec-asm');
|
||||
}
|
||||
|
||||
define(rqDefZD, function() {
|
||||
// DEV: zstddec.js has been compiled with `-s EXPORT_NAME="ZD" -s MODULARIZE=1` to avoid a clash with xzdec.js
|
||||
// Note that we include zstddec-wasm or zstddec-asm above in requireJS definition, but we cannot change the name in the function list
|
||||
// For explanation of loading method below to avoid conflicts, see https://github.com/emscripten-core/emscripten/blob/master/src/settings.js
|
||||
|
||||
/**
|
||||
@ -39,7 +53,8 @@ define(['zstddec'], function() {
|
||||
* @type EMSInstanceExt
|
||||
*/
|
||||
var zd;
|
||||
ZD().then(function(instance) {
|
||||
|
||||
var instantiateDecoder = function (instance) {
|
||||
// Instantiate the zd object
|
||||
zd = instance;
|
||||
// Create JS API by wrapping C++ functions
|
||||
@ -82,6 +97,21 @@ define(['zstddec'], function() {
|
||||
zd._outBuffer.ptr = mallocOrDie(3 << 2); // 3 x 32bit bytes
|
||||
// Reserve w/asm memory for the outBuffer data steam
|
||||
zd._outBuffer.dst = mallocOrDie(zd._outBuffer.size);
|
||||
};
|
||||
|
||||
ZD().then(instantiateDecoder)
|
||||
.catch(function (err) {
|
||||
console.debug(err);
|
||||
if (/CompileError.+?WASM/i.test(err.message)) {
|
||||
console.log("WASM failed to load, falling back to ASM...", err);
|
||||
ZD = null;
|
||||
require(['zstddec-asm'], function() {
|
||||
ZD().then(instantiateDecoder)
|
||||
.catch(function (err) {
|
||||
console.error('Could not instantiate any decoder!', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@ -94,7 +124,7 @@ define(['zstddec'], function() {
|
||||
* Is the decompressor already working?
|
||||
* @type Boolean
|
||||
*/
|
||||
appstate.zdBusy = false;
|
||||
var busy = false;
|
||||
|
||||
/**
|
||||
* @typedef Decompressor
|
||||
@ -113,6 +143,7 @@ define(['zstddec'], function() {
|
||||
function Decompressor(reader) {
|
||||
this._reader = reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the decompression stream, and initiate a read loop to decompress from the beginning of the cluster
|
||||
* until we reach <offset> in the decompressed byte stream
|
||||
@ -121,7 +152,7 @@ define(['zstddec'], function() {
|
||||
* @returns {Promise<ArrayBuffer>} Promise for an ArrayBuffer with decoded data
|
||||
*/
|
||||
Decompressor.prototype.readSlice = function(offset, length) {
|
||||
appstate.zdBusy = true;
|
||||
busy = true;
|
||||
this._inStreamPos = 0;
|
||||
this._inStreamChunkedPos = 0;
|
||||
this._outStreamPos = 0;
|
||||
@ -139,7 +170,7 @@ define(['zstddec'], function() {
|
||||
// Should you need to free the decoder stream handle, use command below, but be sure to create a new stream control object
|
||||
// before attempting further decompression
|
||||
// zd._ZSTD_freeDStream(zd._decHandle);
|
||||
appstate.zdBusy = false;
|
||||
busy = false;
|
||||
return data;
|
||||
});
|
||||
};
|
||||
@ -152,7 +183,8 @@ define(['zstddec'], function() {
|
||||
* @returns {Promise} A Promise for the readSlice() function
|
||||
*/
|
||||
Decompressor.prototype.readSliceSingleThread = function (offset, length) {
|
||||
if (zd && !appstate.zdBusy) {
|
||||
// Tests whether the decompressor is ready (initiated) and not busy
|
||||
if (zd && !busy) {
|
||||
return this.readSlice(offset, length);
|
||||
} else {
|
||||
// The decompressor is already in progress.
|
||||
@ -260,4 +292,4 @@ define(['zstddec'], function() {
|
||||
return {
|
||||
Decompressor: Decompressor
|
||||
};
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user