diff --git a/KiwixWebApp.jsproj b/KiwixWebApp.jsproj
index 9cc86166..f6123867 100644
--- a/KiwixWebApp.jsproj
+++ b/KiwixWebApp.jsproj
@@ -299,7 +299,7 @@
-
+
diff --git a/pwabuilder-sw.js b/pwabuilder-sw.js
index 014295f9..96afb347 100644
--- a/pwabuilder-sw.js
+++ b/pwabuilder-sw.js
@@ -51,7 +51,7 @@ const precacheFiles = [
"www/js/init.js",
"www/js/lib/bootstrap.js",
"www/js/lib/bootstrap.min.js",
- "www/js/lib/cookies.js",
+ "www/js/lib/settingsStore.js",
"www/js/lib/filecache.js",
"www/js/lib/images.js",
"www/js/lib/jquery-3.2.1.slim.js",
diff --git a/www/js/init.js b/www/js/init.js
index f53b4df0..c44ce53b 100644
--- a/www/js/init.js
+++ b/www/js/init.js
@@ -56,7 +56,7 @@ params['fileVersion'] = "wikipedia_en_100_maxi_2020-12.zim (23-Dec-2020)"; //Use
params['cachedStartPage'] = false; //If you have cached the start page for quick start, give its URI here
params['kiwixDownloadLink'] = "https://download.kiwix.org/zim/"; //Include final slash
-params['cookieSupport'] = checkCookies();
+params['storeType'] = checkCookies();
params['maxResults'] = ~~(getCookie('maxResults') || 25); //Number of search results to display
params['relativeFontSize'] = ~~(getCookie('relativeFontSize') || 100); //Sets the initial font size for articles (as a percentage) - user can adjust using zoom buttons
params['relativeUIFontSize'] = ~~(getCookie('relativeUIFontSize') || 100); //Sets the initial font size for UI (as a percentage) - user can adjust using slider in Config
@@ -92,7 +92,7 @@ params['localStorage'] = params['localStorage'] || "";
params['pickedFile'] = launchArguments ? launchArguments.files[0] : "";
params['pickedFolder'] = params['pickedFolder'] || "";
params['lastPageVisit'] = getCookie('lastPageVisit') || "";
-params['lastPageVisit'] = params['lastPageVisit'] ? decodeURIComponent(params['lastPageVisit']) : "";
+params.lastPageVisit = params.lastPageVisit ? decodeURIComponent(params.lastPageVisit): "";
params['themeChanged'] = params['themeChanged'] || false;
params['allowInternetAccess'] = params['allowInternetAccess'] || false; //Do not get value from cookie, should be explicitly set by user on a per-session basis
params['printIntercept'] = false;
@@ -146,10 +146,6 @@ document.getElementById('hideToolbarsCheck').indeterminate = params.hideToolbars
document.getElementById('hideToolbarsCheck').readOnly = params.hideToolbars === "top";
document.getElementById('hideToolbarsState').innerHTML = (params.hideToolbars === "top" ? "top" : params.hideToolbars ? "both" : "never");
-//Set up packaged Electron app
-if (!params.pickedFile && params.storedFile && typeof window.fs !== 'undefined') {
- params.pickedFile = params.storedFile;
-}
//Set up storage types
if (params.storedFile && typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') { //UWP
Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync(params.archivePath).done(function (folder) {
@@ -245,7 +241,7 @@ window.addEventListener('appinstalled', function(e) {
function getCookie(name) {
var result;
- if (params.cookieSupport == 'cookie') {
+ if (params.storeType == 'cookie') {
var regexp = new RegExp('(?:^|;)\\s*' + name + '=([^;]+)(?:;|$)');
result = document.cookie.match(regexp);
result = result && result.length > 1 ? result[1] : null;
diff --git a/www/js/lib/cookies.js b/www/js/lib/cookies.js
deleted file mode 100644
index ba0c611e..00000000
--- a/www/js/lib/cookies.js
+++ /dev/null
@@ -1,117 +0,0 @@
-'use strict';
-define([], function() {
-/*\
-|*|
-|*| :: cookies.js ::
-|*|
-|*| A complete cookies reader/writer framework with full unicode support.
-|*|
-|*| https://developer.mozilla.org/en-US/docs/DOM/document.cookie
-|*|
-|*| This framework is released under the GNU Public License, version 3 or later.
-|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
-|*|
-|*| Syntaxes:
-|*|
-|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
-|*| * docCookies.getItem(name)
-|*| * docCookies.removeItem(name[, path])
-|*| * docCookies.hasItem(name)
-|*| * docCookies.keys()
-|*|
-\*/
-
-// Test for cookie support
-var storeType = 'cookie';
-document.cookie = 'kiwixCookie=working;expires=Fri, 31 Dec 9999 23:59:59 GMT';
-var kiwixCookie = /kiwixCookie=working/i.test(document.cookie);
-if (kiwixCookie) {
- document.cookie = 'kiwixCookie=broken;expires=Fri, 31 Dec 9999 23:59:59 GMT';
- kiwixCookie = !/kiwixCookie=working/i.test(document.cookie);
-}
-document.cookie = 'kiwixCookie=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
-if (!kiwixCookie) {
- // Cookies appear to be blocked, so test for localStorage support
- var result = false;
- try {
- result = 'localStorage' in window && window['localStorage'] !== null;
- } catch (e) {
- console.log('LocalStorage is not supported!');
- }
- if (result) storeType = 'local_storage';
-}
-console.log('Test2: storeType: ' + storeType);
-
-var docCookies = {
- getItem: function (sKey) {
- if (!sKey) {
- return null;
- }
- if (params.storeType !== 'local_storage') {
- return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
- } else {
- return localStorage.getItem(keyPrefix + sKey);
- }
- },
- setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
- if (params.storeType !== 'local_storage') {
- if (!sKey || /^(?:expires|max-age|path|domain|secure)$/i.test(sKey)) {
- return false;
- }
- var sExpires = "";
- if (vEnd) {
- switch (vEnd.constructor) {
- case Number:
- sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
- break;
- case String:
- sExpires = "; expires=" + vEnd;
- break;
- case Date:
- sExpires = "; expires=" + vEnd.toUTCString();
- break;
- }
- }
- document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
- } else {
- localStorage.setItem(keyPrefix + sKey, sValue);
- }
- return true;
- },
- removeItem: function (sKey, sPath, sDomain) {
- if (!this.hasItem(sKey)) {
- return false;
- }
- if (params.storeType !== 'local_storage') {
- document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
- } else {
- localStorage.removeItem(keyPrefix + sKey);
- }
- return true;
- },
- hasItem: function (sKey) {
- if (!sKey) {
- return false;
- }
- if (params.storeType !== 'local_storage') {
- return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
- } else {
- return localStorage.getItem(keyPrefix + sKey) === null ? false : true;
- }
- },
- _cookieKeys: function () {
- var aKeys = document.cookie.replace(/((?:^|\s*;)[^=]+)(?=;|$)|^\s*|\s*(?:=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:=[^;]*)?;\s*/);
- for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
- aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
- }
- return aKeys;
- }
-};
-
- return {
- getItem: docCookies.getItem,
- setItem: docCookies.setItem,
- removeItem: docCookies.removeItem,
- hasItem: docCookies.hasItem
- };
-});
\ No newline at end of file
diff --git a/www/js/lib/settingsStore.js b/www/js/lib/settingsStore.js
new file mode 100644
index 00000000..bc076c3d
--- /dev/null
+++ b/www/js/lib/settingsStore.js
@@ -0,0 +1,171 @@
+'use strict';
+define([], function () {
+ /**
+ * settingsStore.js
+ *
+ * A reader/writer framework for cookies or localStorage with full unicode support based on the Mozilla cookies framework.
+ * The Mozilla code has been adapted to test for the availability of the localStorage API, and to use it in preference to settingsStore.
+ *
+ * Mozilla version information:
+ *
+ * Revision #1 - September 4, 2014
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
+ * https://developer.mozilla.org/User:fusionchess
+ *
+ * This framework is released under the GNU Public License, version 3 or later.
+ * http://www.gnu.org/licenses/gpl-3.0-standalone.html
+ *
+ * Syntaxes:
+ *
+ * * settingsStore.setItem(name, value[, end[, path[, domain[, secure]]]])
+ * * settingsStore.getItem(name)
+ * * settingsStore.removeItem(name[, path[, domain]])
+ * * settingsStore.hasItem(name)
+ *
+ */
+
+ /**
+ * A RegExp of the settings keys used in the cookie that should be migrated to localStorage if the API is available
+ * DEV: It should not be necessary to keep this list up-to-date because any keys added after this list was created
+ * (April 2020) will already be stored in localStorage if it is available to the client's browser or platform and
+ * will not need to be migrated
+ * @type {RegExp}
+ */
+ var regexpCookieKeysToMigrate = new RegExp([
+ 'lastContentInjectionMode', 'lastSelectedArchivePath', 'imageDisplay', 'useMathJax', 'version', 'lastSelectedArchive',
+ 'listOfArchives', 'lastPageVisit', 'cssUITheme', 'cssTheme', 'cssSource', 'removePageMaxWidth', 'hideActiveContentWarning',
+ 'allowHTMLExtraction'
+ ].join('|'));
+
+ /**
+ * A constant to set the prefix that will be added to keys when stored in localStorage: this is used to prevent
+ * potential collision of key names with localStorage keys used by code inside ZIM archives
+ * @type {String}
+ */
+ const keyPrefix = 'kiwixjs-';
+
+ // Tests for available Storage APIs (document.cookie or localStorage) and returns the best available of these
+ function getBestAvailableStorageAPI() {
+ // DEV: In FF extensions, cookies are blocked since at least FF 68.6 but possibly since FF 55 [kiwix-js #612]
+ var type = 'none';
+ // First test for localStorage API support
+ var localStorageTest;
+ try {
+ localStorageTest = 'localStorage' in window && window['localStorage'] !== null;
+ // DEV: Above test returns true in IE11 running from file:// protocol, but attempting to write a key to
+ // localStorage causes an exception; so to test fully, we must now attempt to write and remove a test key
+ if (localStorageTest) {
+ localStorage.setItem('tempKiwixStorageTest', '');
+ localStorage.removeItem('tempKiwixStorageTest');
+ }
+ } catch (e) {
+ localStorageTest = false;
+ }
+ // Now test for document.cookie API support
+ document.cookie = 'tempKiwixCookieTest=working; expires=Fri, 31 Dec 9999 23:59:59 GMT; SameSite=Strict';
+ var kiwixCookieTest = /tempKiwixCookieTest=working/.test(document.cookie);
+ // Remove test value by expiring the key
+ document.cookie = 'tempKiwixCookieTest=; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict';
+ if (kiwixCookieTest) type = 'cookie';
+ // Prefer localStorage if supported due to some platforms removing cookies once the session ends in some contexts
+ if (localStorageTest) type = 'local_storage';
+ // If both cookies and localStorage are supported, and document.cookie contains keys to migrate,
+ // migrate settings to use localStorage
+ if (kiwixCookieTest && localStorageTest && regexpCookieKeysToMigrate.test(document.cookie)) _migrateStorageSettings();
+ // Note that if this function returns 'none', the cookie implementations below will run anyway. This is because storing a cookie
+ // does not cause an exception even if cookies are blocked in some contexts, whereas accessing localStorage may cause an exception
+ return type;
+ }
+
+ var settingsStore = {
+ getItem: function (sKey) {
+ if (!sKey) {
+ return null;
+ }
+ if (params.storeType !== 'local_storage') {
+ return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
+ } else {
+ return localStorage.getItem(keyPrefix + sKey);
+ }
+ },
+ setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
+ if (params.storeType !== 'local_storage') {
+ if (!sKey || /^(?:expires|max-age|path|domain|secure)$/i.test(sKey)) {
+ return false;
+ }
+ var sExpires = "";
+ if (vEnd) {
+ switch (vEnd.constructor) {
+ case Number:
+ sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
+ break;
+ case String:
+ sExpires = "; expires=" + vEnd;
+ break;
+ case Date:
+ sExpires = "; expires=" + vEnd.toUTCString();
+ break;
+ }
+ }
+ document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
+ } else {
+ localStorage.setItem(keyPrefix + sKey, sValue);
+ }
+ return true;
+ },
+ removeItem: function (sKey, sPath, sDomain) {
+ if (!this.hasItem(sKey)) {
+ return false;
+ }
+ if (params.storeType !== 'local_storage') {
+ document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
+ } else {
+ localStorage.removeItem(keyPrefix + sKey);
+ }
+ return true;
+ },
+ hasItem: function (sKey) {
+ if (!sKey) {
+ return false;
+ }
+ if (params.storeType !== 'local_storage') {
+ return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
+ } else {
+ return localStorage.getItem(keyPrefix + sKey) === null ? false : true;
+ }
+ },
+ _cookieKeys: function () {
+ var aKeys = document.cookie.replace(/((?:^|\s*;)[^=]+)(?=;|$)|^\s*|\s*(?:=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:=[^;]*)?;\s*/);
+ for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
+ aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
+ }
+ return aKeys;
+ }
+ };
+
+ // One-off migration of storage settings from cookies to localStorage
+ function _migrateStorageSettings() {
+ console.log('Migrating Settings Store from cookies to localStorage...');
+ var cookieKeys = settingsStore._cookieKeys();
+ // Note that because migration occurs before setting params.storeType, settingsStore.getItem() will get the item from
+ // document.cookie instead of localStorage, which is the intended behaviour
+ for (var i = 0; i < cookieKeys.length; i++) {
+ if (regexpCookieKeysToMigrate.test(cookieKeys[i])) {
+ var migratedKey = keyPrefix + cookieKeys[i];
+ localStorage.setItem(migratedKey, settingsStore.getItem(cookieKeys[i]));
+ settingsStore.removeItem(cookieKeys[i]);
+ console.log('- ' + migratedKey);
+ }
+ }
+ console.log('Migration done.');
+ }
+
+ return {
+ getItem: settingsStore.getItem,
+ setItem: settingsStore.setItem,
+ removeItem: settingsStore.removeItem,
+ hasItem: settingsStore.hasItem,
+ getBestAvailableStorageAPI: getBestAvailableStorageAPI
+ };
+});