diff options
-rw-r--r-- | README.txt | 9 | ||||
-rw-r--r-- | TODOS.org | 23 | ||||
-rw-r--r-- | background/main.js | 262 | ||||
-rw-r--r-- | background/message_server.js | 41 | ||||
-rw-r--r-- | background/page_actions_server.js | 264 | ||||
-rw-r--r-- | background/policy_injector.js | 114 | ||||
-rw-r--r-- | background/settings_query.js | 204 | ||||
-rw-r--r-- | background/storage.js | 654 | ||||
-rw-r--r-- | background/storage_server.js | 95 | ||||
-rwxr-xr-x | build.sh | 289 | ||||
-rw-r--r-- | common/browser.js | 24 | ||||
-rw-r--r-- | common/connection_types.js | 18 | ||||
-rw-r--r-- | common/gen_unique.js | 42 | ||||
-rw-r--r-- | common/lock.js | 68 | ||||
-rw-r--r-- | common/once.js | 50 | ||||
-rw-r--r-- | common/sha256.js | 673 | ||||
-rw-r--r-- | common/storage_client.js | 360 | ||||
-rw-r--r-- | common/stored_types.js | 46 | ||||
-rw-r--r-- | common/url_item.js | 22 | ||||
-rw-r--r-- | content/freezer.js | 98 | ||||
-rw-r--r-- | content/main.js | 221 | ||||
-rw-r--r-- | content/page_actions.js | 92 | ||||
-rw-r--r-- | copyright | 4 | ||||
-rw-r--r-- | html/display-panel.html | 4 | ||||
-rw-r--r-- | html/display-panel.js | 14 | ||||
-rw-r--r-- | html/options.html | 9 | ||||
-rw-r--r-- | html/options_main.js | 1262 | ||||
-rw-r--r-- | manifest.json | 52 |
28 files changed, 2664 insertions, 2350 deletions
@@ -6,16 +6,17 @@ together. Such facility is necessary to enable browsing World Wide Web without executing nonfree software. Currently, the target browsers for this extension are Ungoogled Chromium -and various forks of Firefox Quantum (57+). +and various forks of Firefox (version 60+). This extension is still in an early stage. See TODOS.org. Also see `https://git.koszko.org/browser-extension-doc/' for documentation in development. ## Installation ## -The extension can be loaded into Ungoogled Chromium or a modern Gecko-based -browser as unpacked extension. As of now, project's main directory is also -the extension directory. +The extension can be "built" with `./build.sh mozilla' or `./build.sh chromium'. +This creates directories build_mozilla/ and build_chromium/, respectively. +Such directory can be loaded into Ungoogled Chromium or a modern Gecko-based +browser as unpacked extension. ## Copyright ## All copyright information is gathered in the `copyright' file which follows @@ -20,20 +20,15 @@ TODO: - make script bag components re-orderable (via drag&drop in options page) -- CRUCIAL - find some way not to require each chrome user to modify manifest.json - test with more browser forks (Abrowser, Parabola IceWeasel, LibreWolf) - - also see if browsers based on pre-quantum FF support enough of - WebExtensions for easy porting - make sure page's own csp in <head> doesn't block our scripts -- find out how and make it possible to whitelist non-https urls and - whether we can inject csp to them - create a repository to host scripts - enable the extension to automatically fetch script substitutes from the repo - make it possible to inject scripts to arbitrary places in DOM - make script blocking code omit those scripts - check if prerendering has to be blocked -- CRUCIAL - block prefetch -- rearrange files in extension, add some mechanism to build the extension -- all solutions to modularize js code SUCK; come up with own simple DSL - to manage imports/exports +- rearrange files in extension +- supplement the build script with a makefile, also produce zipped arifacts - perform never-ending refactoring of already-written code - also implement support for whitelisting of non-https urls - validate data entered in settings @@ -49,11 +44,21 @@ TODO: (unless someone suggests another good name before we do so) - add an option to disable script blocking globally - Add support to settings_query for non-standard URLs - (e.g. file:// and about:) + (e.g. file:// and ftp://) - Process HTML files in data: URLs instead of just blocking them +- improve CSP injection for pathological cases like <script> before <head> +- Fix FF script blocking and whitelisting (FF seems to be by itself repeatedly + injecting CSP headers that were injected once, this makes it impossible to + whielist site that was unwhitelisted before; FF also seems to be removing our + injected script's nonce for no reason 🙁) DONE: -- make blocking more torough -- DONE 2021-06-28 +- find out if we can successfully use CSP to block file:// under FF -- DONE 2021-06-30 +- come up with own simple DSL to manage imports/exports -- DONE 2021-06-30 +- add some mechanism to build the extension -- DONE 2021-06-30 +- see if browsers based on pre-quantum FF support enough of -- DONE 2021-06-29 + WebExtensions for easy porting (no, those we know dropped the support) +- make blocking more thorough -- DONE 2021-06-28 - mind the data: urls -- CRUCIAL - employ copyright file in Debian format -- DONE 2021-06-25 - find out what causes storage sometimes not to get initialized under IceCat 60 -- DONE 2021-06-23 diff --git a/background/main.js b/background/main.js index ca56e37..aec25b6 100644 --- a/background/main.js +++ b/background/main.js @@ -5,127 +5,128 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const TYPE_PREFIX = window.TYPE_PREFIX; - const get_storage = window.get_storage; - const start_storage_server = window.start_storage_server; - const start_page_actions_server = window.start_page_actions_server; - const start_policy_injector = window.start_policy_injector; - const browser = window.browser; - - start_storage_server(); - start_page_actions_server(); - start_policy_injector(); - - async function init_myext(install_details) - { - console.log("details:", install_details); - if (install_details.reason != "install") - return; - - let storage = await get_storage(); - - await storage.clear(); - - /* - * Below we add sample settings to the extension. - * Those should be considered example values for viewing in the options - * page. They won't make my.fsf.org work. The only scripts that does - * something useful right now is the opencores one. - */ - - let components = []; - for (let script_data of [ - {url: "http://127.0.0.1:8000/myfsf_define_CRM.js", - hash:"bf0cc81c7e8d5f800877b4bc3f14639f946f5ac6d4dc120255ffac5eba5e48fe"}, - {url: "https://my.fsf.org/misc/jquery.js?v=1.4.4", - hash:"261ae472fa0cbf27c80c9200a1599a60fde581a0e652eee4bf41def8cb61f2d0"}, - {url: "https://my.fsf.org/misc/jquery-extend-3.4.0.js?v=1.4.4", - hash:"c54103ba57ee210ca55c052e70415402707548a4e6a68dd6efb3895019bee392"}, - {url: "https://my.fsf.org/misc/jquery-html-prefilter-3.5.0-backport.js?v=1.4.4", - hash:"fad84efa145fb507e5df9b582fa01b1c4e6313de7f72ebdd55726d92fa4dbf06"}, - {url: "https://my.fsf.org/misc/jquery.once.js?v=1.2", - hash:"1430f42c0d760ba8e05bb3762480502e541f654fec5739ee40625ab22dc38c4f"}, - {url: "https://my.fsf.org/misc/drupal.js?qmaukd", - hash:"2e08dccbd4d8b728a6871562995a4636b89bfe0ed3b8fb0138191c922228b116"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery/dist/jquery.min.js?qmaukd", - hash:"a6d01520d28d15dbe476de84eea90eb3ee2d058722efc062ec73cb5fad78a17b"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-ui/jquery-ui.min.js?qmaukd", - hash:"28ce75d953678c4942df47a11707a15e3c756021cf89090e3e6aa7ad6b6971c3"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/lodash-compat/lodash.min.js?qmaukd", - hash:"f2871cc80c52fe8c04c582c4a49797c9c8fd80391cf1452e47f7fe97835ed5cc"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.mousewheel.min.js?qmaukd", - hash:"f50233e84c2ac7ada37a094d3f7d3b3f7c97716d6b7b47bf69619d93ee4ac1ce"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/select2/select2.min.js?qmaukd", - hash:"ce61298fb9aa4ec49ccd4172d097e36a9e5db3af06a7b82796659368f15b7c1b"}, - {url: "https://my.fsf.org/sites/all/modules/civi crm/packages/jquery/plugins/jquery.form.min.js?qmaukd", - hash:"c90f0e501d2948fbc2b61bffd654fa4ab64741fd48923782419eeb14d3816fb8"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.timeentry.min.js?qmaukd", - hash:"8e85df981e8ad7049d06dfb075277d038734d36a7097c7f021021b2bdccfe9bb"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.blockUI.min.js?qmaukd", - hash:"806aedff52ac822f2adc5797073e1e5c5cec32eb9f15f2319cb32a347dcd232b"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/datatables/media/js/jquery.dataTables.min.js?qmaukd", - hash:"b796504d9b1b422f0dc6ccc2d740ac78a8c9e5078cc3934836d39742b1121925"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-validation/dist/jquery.validate.min.js?qmaukd", - hash:"f0f5373ad203101ea91bf826c5a7ef8f7cd74887f06bad2cb9277a504503b9e2"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.ui.datepicker.validation.min.js?qmaukd", - hash:"c6e6f6bf7f8fff25cca338045774e267e8eaa2d48ac9100540f3d59a6d2b3c61"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/js/Common.js?qmaukd", - hash:"17aa222a3af2e8958be16accb5e77ef39f67009cb3b500718d8fffd45b399148"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.datepicker.js?qmaukd", - hash:"9bd8d10208aa99c156325f7819da6f0dd62ba221ac4119c3ccd4834e2cf36535"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.ajax.js?qmaukd", - hash:"6401a4e257b7499ae4a00be2c200e4504a2c9b3d6b278a830c31a7b63374f0fe"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/js/wysiwyg/crm.wysiwyg.js?qmaukd", - hash:"fa962356072a36672c3b4b25bdeb657f020995a067e20a29cd5bb84b05157762"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/js/noconflict.js?qmaukd", - hash:"58d6d9f496a235d23cf891926d71f2104e4f2afe1d14bb4e2b5233f646c35e62"}, - {url: "https://my.fsf.org/sites/all/modules/matomo/matomo.js?qmaukd", - hash:"7f39ccd085f348189cd2fb62ea4d4a658d96f6bba266265880b98605e777e2de"}, - {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/global.js?qmaukd", - hash:"aa7983f6b902f9f4415cfc8346e0c3f194cc95b78f52f2ad09ec7effa1326b9c"}, - {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.superfish.min.js?qmaukd", - hash:"5ef1f93bf3901227056bf9ed0ed93a148eec4dda30f419756b12bedd1098815e"}, - {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.sidr.min.js?qmaukd", - hash:"c4914d415826676c6af2e61f16edb72c5388f8600ba6de9049892aee49d980a0"}, - {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.flexslider.min.js?qmaukd", - hash:"cefaf715761b4494913851249b9d40dacb4a8cb61242b0efc859dc586d56e0d4"}, - {url: "http://127.0.0.1:8000/myfsf_crap.js", - hash:"d91ccf21592d0f861ea0ba946bc257fc5d88269327cad0a91387da6cb8ff633e"}, - {url: "https://my.fsf.org/sites/all/modules/civicrm/templates/CRM/Core/BillingBlock.js?r=lp7Di", - hash:"2f25d35e7a0c0060ab0a444a577f09dd3c9934ae898a7ee0eb20b6c986ab5a1c"}, - {url: "https://my.fsf.org/extensions/com.aghstrategies.giftmemberships/js/giftpricefield.js?r=lp7Di", - hash:"f86080e6bd306fe46474039aeca2808235005bce5a2a29416d08210022039a45"}, - {url: "https://my.fsf.org/extensions/com.ginkgostreet.negativenegator/js/negativenegator.js?r=lp7Di", - hash:"d0e87bac832856db70947d82a7ab4e0b7c8b1070d5f1a32335345e033ece3a14"} - ]) { - let name_regex = /\/([^/]+)\.js/; - let name = name_regex.exec(script_data.url)[1]; - await storage.set(TYPE_PREFIX.SCRIPT, name, script_data); - components.push([TYPE_PREFIX.SCRIPT, name]); - } - - await storage.set(TYPE_PREFIX.BAG, "myfsf_join", components); - - await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/join", { - components: [TYPE_PREFIX.BAG, "myfsf_join"] - }); - - let hello_script = { - text: "console.log(\"hello, every1!\");\n" - }; - await storage.set(TYPE_PREFIX.SCRIPT, "hello", hello_script); - await storage.set(TYPE_PREFIX.BAG, "hello", - [[TYPE_PREFIX.SCRIPT, "hello"]]); - await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/", { - components: [TYPE_PREFIX.BAG, "hello"], - allow: true - }); - - let opencores_script = { - text: `\ +/* + * IMPORTS_START + * IMPORT TYPE_PREFIX + * IMPORT get_storage + * IMPORT start_storage_server + * IMPORT start_page_actions_server + * IMPORT start_policy_injector + * IMPORT browser + * IMPORTS_END + */ + +start_storage_server(); +start_page_actions_server(); +start_policy_injector(); + +async function init_myext(install_details) +{ + console.log("details:", install_details); + if (install_details.reason != "install") + return; + + let storage = await get_storage(); + + await storage.clear(); + + /* + * Below we add sample settings to the extension. + * Those should be considered example values for viewing in the options + * page. They won't make my.fsf.org work. The only scripts that does + * something useful right now is the opencores one. + */ + + let components = []; + for (let script_data of [ + {url: "http://127.0.0.1:8000/myfsf_define_CRM.js", + hash:"bf0cc81c7e8d5f800877b4bc3f14639f946f5ac6d4dc120255ffac5eba5e48fe"}, + {url: "https://my.fsf.org/misc/jquery.js?v=1.4.4", + hash:"261ae472fa0cbf27c80c9200a1599a60fde581a0e652eee4bf41def8cb61f2d0"}, + {url: "https://my.fsf.org/misc/jquery-extend-3.4.0.js?v=1.4.4", + hash:"c54103ba57ee210ca55c052e70415402707548a4e6a68dd6efb3895019bee392"}, + {url: "https://my.fsf.org/misc/jquery-html-prefilter-3.5.0-backport.js?v=1.4.4", + hash:"fad84efa145fb507e5df9b582fa01b1c4e6313de7f72ebdd55726d92fa4dbf06"}, + {url: "https://my.fsf.org/misc/jquery.once.js?v=1.2", + hash:"1430f42c0d760ba8e05bb3762480502e541f654fec5739ee40625ab22dc38c4f"}, + {url: "https://my.fsf.org/misc/drupal.js?qmaukd", + hash:"2e08dccbd4d8b728a6871562995a4636b89bfe0ed3b8fb0138191c922228b116"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery/dist/jquery.min.js?qmaukd", + hash:"a6d01520d28d15dbe476de84eea90eb3ee2d058722efc062ec73cb5fad78a17b"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-ui/jquery-ui.min.js?qmaukd", + hash:"28ce75d953678c4942df47a11707a15e3c756021cf89090e3e6aa7ad6b6971c3"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/lodash-compat/lodash.min.js?qmaukd", + hash:"f2871cc80c52fe8c04c582c4a49797c9c8fd80391cf1452e47f7fe97835ed5cc"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.mousewheel.min.js?qmaukd", + hash:"f50233e84c2ac7ada37a094d3f7d3b3f7c97716d6b7b47bf69619d93ee4ac1ce"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/select2/select2.min.js?qmaukd", + hash:"ce61298fb9aa4ec49ccd4172d097e36a9e5db3af06a7b82796659368f15b7c1b"}, + {url: "https://my.fsf.org/sites/all/modules/civi crm/packages/jquery/plugins/jquery.form.min.js?qmaukd", + hash:"c90f0e501d2948fbc2b61bffd654fa4ab64741fd48923782419eeb14d3816fb8"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.timeentry.min.js?qmaukd", + hash:"8e85df981e8ad7049d06dfb075277d038734d36a7097c7f021021b2bdccfe9bb"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.blockUI.min.js?qmaukd", + hash:"806aedff52ac822f2adc5797073e1e5c5cec32eb9f15f2319cb32a347dcd232b"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/datatables/media/js/jquery.dataTables.min.js?qmaukd", + hash:"b796504d9b1b422f0dc6ccc2d740ac78a8c9e5078cc3934836d39742b1121925"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-validation/dist/jquery.validate.min.js?qmaukd", + hash:"f0f5373ad203101ea91bf826c5a7ef8f7cd74887f06bad2cb9277a504503b9e2"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.ui.datepicker.validation.min.js?qmaukd", + hash:"c6e6f6bf7f8fff25cca338045774e267e8eaa2d48ac9100540f3d59a6d2b3c61"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/js/Common.js?qmaukd", + hash:"17aa222a3af2e8958be16accb5e77ef39f67009cb3b500718d8fffd45b399148"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.datepicker.js?qmaukd", + hash:"9bd8d10208aa99c156325f7819da6f0dd62ba221ac4119c3ccd4834e2cf36535"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.ajax.js?qmaukd", + hash:"6401a4e257b7499ae4a00be2c200e4504a2c9b3d6b278a830c31a7b63374f0fe"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/js/wysiwyg/crm.wysiwyg.js?qmaukd", + hash:"fa962356072a36672c3b4b25bdeb657f020995a067e20a29cd5bb84b05157762"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/js/noconflict.js?qmaukd", + hash:"58d6d9f496a235d23cf891926d71f2104e4f2afe1d14bb4e2b5233f646c35e62"}, + {url: "https://my.fsf.org/sites/all/modules/matomo/matomo.js?qmaukd", + hash:"7f39ccd085f348189cd2fb62ea4d4a658d96f6bba266265880b98605e777e2de"}, + {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/global.js?qmaukd", + hash:"aa7983f6b902f9f4415cfc8346e0c3f194cc95b78f52f2ad09ec7effa1326b9c"}, + {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.superfish.min.js?qmaukd", + hash:"5ef1f93bf3901227056bf9ed0ed93a148eec4dda30f419756b12bedd1098815e"}, + {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.sidr.min.js?qmaukd", + hash:"c4914d415826676c6af2e61f16edb72c5388f8600ba6de9049892aee49d980a0"}, + {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.flexslider.min.js?qmaukd", + hash:"cefaf715761b4494913851249b9d40dacb4a8cb61242b0efc859dc586d56e0d4"}, + {url: "http://127.0.0.1:8000/myfsf_crap.js", + hash:"d91ccf21592d0f861ea0ba946bc257fc5d88269327cad0a91387da6cb8ff633e"}, + {url: "https://my.fsf.org/sites/all/modules/civicrm/templates/CRM/Core/BillingBlock.js?r=lp7Di", + hash:"2f25d35e7a0c0060ab0a444a577f09dd3c9934ae898a7ee0eb20b6c986ab5a1c"}, + {url: "https://my.fsf.org/extensions/com.aghstrategies.giftmemberships/js/giftpricefield.js?r=lp7Di", + hash:"f86080e6bd306fe46474039aeca2808235005bce5a2a29416d08210022039a45"}, + {url: "https://my.fsf.org/extensions/com.ginkgostreet.negativenegator/js/negativenegator.js?r=lp7Di", + hash:"d0e87bac832856db70947d82a7ab4e0b7c8b1070d5f1a32335345e033ece3a14"} + ]) { + let name_regex = /\/([^/]+)\.js/; + let name = name_regex.exec(script_data.url)[1]; + await storage.set(TYPE_PREFIX.SCRIPT, name, script_data); + components.push([TYPE_PREFIX.SCRIPT, name]); + } + + await storage.set(TYPE_PREFIX.BAG, "myfsf_join", components); + + await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/join", { + components: [TYPE_PREFIX.BAG, "myfsf_join"] + }); + + let hello_script = { + text: "console.log(\"hello, every1!\");\n" + }; + await storage.set(TYPE_PREFIX.SCRIPT, "hello", hello_script); + await storage.set(TYPE_PREFIX.BAG, "hello", + [[TYPE_PREFIX.SCRIPT, "hello"]]); + await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/", { + components: [TYPE_PREFIX.BAG, "hello"], + allow: true + }); + + let opencores_script = { + text: `\ let data = JSON.parse(document.getElementById("__NEXT_DATA__").textContent); let sections = {}; for (let h1 of document.getElementsByClassName("cMJCrc")) { @@ -154,16 +155,15 @@ for (let prop of data.props.pageProps.list) { ul.appendChild(li); } ` - }; + }; - await storage.set(TYPE_PREFIX.SCRIPT, "opencores", opencores_script); - await storage.set(TYPE_PREFIX.PAGE, "https://opencores.org/projects", { - components: [TYPE_PREFIX.SCRIPT, "opencores"], - allow: false - }); - } + await storage.set(TYPE_PREFIX.SCRIPT, "opencores", opencores_script); + await storage.set(TYPE_PREFIX.PAGE, "https://opencores.org/projects", { + components: [TYPE_PREFIX.SCRIPT, "opencores"], + allow: false + }); +} - browser.runtime.onInstalled.addListener(init_myext); +browser.runtime.onInstalled.addListener(init_myext); - console.log("hello, myext"); -})(); +console.log("hello, myext"); diff --git a/background/message_server.js b/background/message_server.js index 358e9d5..a541a04 100644 --- a/background/message_server.js +++ b/background/message_server.js @@ -5,28 +5,33 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const browser = window.browser; +/* + * IMPORTS_START + * IMPORT browser + * IMPORTS_END + */ - var listeners = {}; +var listeners = {}; - /* magic should be one of the constants from /common/connection_types.js */ +/* magic should be one of the constants from /common/connection_types.js */ - function listen_for_connection(magic, cb) - { - listeners[magic] = cb; - } +function listen_for_connection(magic, cb) +{ + listeners[magic] = cb; +} - function raw_listen(port) { - if (listeners[port.name] === undefined) - return; +function raw_listen(port) +{ + if (listeners[port.name] === undefined) + return; - listeners[port.name](port); - } + listeners[port.name](port); +} - browser.runtime.onConnect.addListener(raw_listen); +browser.runtime.onConnect.addListener(raw_listen); - window.listen_for_connection = listen_for_connection; -})(); +/* + * EXPORTS_START + * EXPORT listen_for_connection + * EXPORTS_END + */ diff --git a/background/page_actions_server.js b/background/page_actions_server.js index dbf4db3..f9773f6 100644 --- a/background/page_actions_server.js +++ b/background/page_actions_server.js @@ -5,145 +5,149 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const get_storage = window.get_storage; - const TYPE_PREFIX = window.TYPE_PREFIX; - const CONNECTION_TYPE = window.CONNECTION_TYPE; - const browser = window.browser; - const listen_for_connection = window.listen_for_connection; - const sha256 = window.sha256; - const get_query_best = window.get_query_best; - - var storage; - var query_best; - var handler; - - function send_scripts(url, port) - { - let [pattern, settings] = query_best(url); - if (settings === undefined) - return; - - let components = settings.components; - let processed_bags = new Set(); - - if (components !== undefined) - send_scripts_rec([components], port, processed_bags); - } +/* + * IMPORTS_START + * IMPORT get_storage + * IMPORT TYPE_PREFIX + * IMPORT CONNECTION_TYPE + * IMPORT browser + * IMPORT listen_for_connection + * IMPORT sha256 + * IMPORT get_query_best + * IMPORTS_END + */ - // TODO: parallelize script fetching - async function send_scripts_rec(components, port, processed_bags) - { - for (let [prefix, name] of components) { - if (prefix === TYPE_PREFIX.BAG) { - if (processed_bags.has(name)) { - console.log(`preventing recursive inclusion of bag ${name}`); - continue; - } - - var bag = storage.get(TYPE_PREFIX.BAG, name); - - if (bag === undefined) { - console.log(`no bag in storage for key ${name}`); - continue; - } - - processed_bags.add(name); - await send_scripts_rec(bag, port, processed_bags); - processed_bags.delete(name); - } else { - let script_text = await get_script_text(name); - if (script_text === undefined) - continue; - - port.postMessage({inject : [script_text]}); +var storage; +var query_best; +var handler; + +function send_scripts(url, port) +{ + let [pattern, settings] = query_best(url); + if (settings === undefined) + return; + + let components = settings.components; + let processed_bags = new Set(); + + if (components !== undefined) + send_scripts_rec([components], port, processed_bags); +} + +// TODO: parallelize script fetching +async function send_scripts_rec(components, port, processed_bags) +{ + for (let [prefix, name] of components) { + if (prefix === TYPE_PREFIX.BAG) { + if (processed_bags.has(name)) { + console.log(`preventing recursive inclusion of bag ${name}`); + continue; } - } - } - async function get_script_text(script_name) - { - try { - let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name); - if (script_data === undefined) { - console.log(`missing data for ${script_name}`); - return; + var bag = storage.get(TYPE_PREFIX.BAG, name); + + if (bag === undefined) { + console.log(`no bag in storage for key ${name}`); + continue; } - let script_text = script_data.text; - if (!script_text) - script_text = await fetch_remote_script(script_data); - return script_text; - } catch (e) { - console.log(e); - } - } - function ajax_callback() - { - if (this.readyState == 4) - this.resolve_callback(this); - } + processed_bags.add(name); + await send_scripts_rec(bag, port, processed_bags); + processed_bags.delete(name); + } else { + let script_text = await get_script_text(name); + if (script_text === undefined) + continue; - function initiate_ajax_request(resolve, method, url) - { - var xhttp = new XMLHttpRequest(); - xhttp.resolve_callback = resolve; - xhttp.onreadystatechange = ajax_callback; - xhttp.open(method, url, true); - xhttp.send(); + port.postMessage({inject : [script_text]}); + } } - - function make_ajax_request(method, url) - { - return new Promise((resolve, reject) => - initiate_ajax_request(resolve, method, url)); +} + +async function get_script_text(script_name) +{ + try { + let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name); + if (script_data === undefined) { + console.log(`missing data for ${script_name}`); + return; + } + let script_text = script_data.text; + if (!script_text) + script_text = await fetch_remote_script(script_data); + return script_text; + } catch (e) { + console.log(e); } - - async function fetch_remote_script(script_data) - { - try { - let xhttp = await make_ajax_request("GET", script_data.url); - if (xhttp.status === 200) { - let computed_hash = sha256(xhttp.responseText); - if (computed_hash !== script_data.hash) { - console.log(`Bad hash for ${script_data.url}\n got ${computed_hash} instead of ${script_data.hash}`); - return; - } - return xhttp.responseText; - } else { - console.log("script not fetched: " + script_data.url); +} + +function ajax_callback() +{ + if (this.readyState == 4) + this.resolve_callback(this); +} + +function initiate_ajax_request(resolve, method, url) +{ + var xhttp = new XMLHttpRequest(); + xhttp.resolve_callback = resolve; + xhttp.onreadystatechange = ajax_callback; + xhttp.open(method, url, true); + xhttp.send(); +} + +function make_ajax_request(method, url) +{ + return new Promise((resolve, reject) => + initiate_ajax_request(resolve, method, url)); +} + +async function fetch_remote_script(script_data) +{ + try { + let xhttp = await make_ajax_request("GET", script_data.url); + if (xhttp.status === 200) { + let computed_hash = sha256(xhttp.responseText); + if (computed_hash !== script_data.hash) { + console.log(`Bad hash for ${script_data.url}\n got ${computed_hash} instead of ${script_data.hash}`); return; } - } catch (e) { - console.log(e); + return xhttp.responseText; + } else { + console.log("script not fetched: " + script_data.url); + return; } + } catch (e) { + console.log(e); } - - function handle_message(port, message, handler) - { - port.onMessage.removeListener(handler[0]); - let url = message.url; - console.log({url}); - send_scripts(url, port); - } - - function new_connection(port) - { - console.log("new page actions connection!"); - let handler = []; - handler.push(m => handle_message(port, m, handler)); - port.onMessage.addListener(handler[0]); - } - - async function start() - { - storage = await get_storage(); - query_best = await get_query_best(); - - listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection); - } - - window.start_page_actions_server = start; -})(); +} + +function handle_message(port, message, handler) +{ + port.onMessage.removeListener(handler[0]); + let url = message.url; + console.log({url}); + send_scripts(url, port); +} + +function new_connection(port) +{ + console.log("new page actions connection!"); + let handler = []; + handler.push(m => handle_message(port, m, handler)); + port.onMessage.addListener(handler[0]); +} + +async function start_page_actions_server() +{ + storage = await get_storage(); + query_best = await get_query_best(); + + listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection); +} + +/* + * EXPORTS_START + * EXPORT start_page_actions_server + * EXPORTS_END + */ diff --git a/background/policy_injector.js b/background/policy_injector.js index d4d22b6..f05a422 100644 --- a/background/policy_injector.js +++ b/background/policy_injector.js @@ -5,71 +5,77 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; +/* + * IMPORTS_START + * IMPORT TYPE_PREFIX + * IMPORT get_storage + * IMPORT browser + * IMPORT is_chrome + * IMPORT gen_unique + * IMPORT url_item + * IMPORT get_query_best + * IMPORTS_END + */ -(() => { - const TYPE_PREFIX = window.TYPE_PREFIX; - const get_storage = window.get_storage; - const browser = window.browser; - const is_chrome = window.is_chrome; - const gen_unique = window.gen_unique; - const url_item = window.url_item; - const get_query_best = window.get_query_best; +var storage; +var query_best; - var storage; - var query_best; +let csp_header_names = { + "content-security-policy" : true, + "x-webkit-csp" : true, + "x-content-security-policy" : true +}; - let csp_header_names = { - "content-security-policy" : true, - "x-webkit-csp" : true, - "x-content-security-policy" : true - }; +function is_noncsp_header(header) +{ + return !csp_header_names[header.name.toLowerCase()]; +} - function is_noncsp_header(header) - { - return !csp_header_names[header.name.toLowerCase()]; - } +function inject(details) +{ + let url = url_item(details.url); - function inject(details) - { - let url = url_item(details.url); + let [pattern, settings] = query_best(url); - let [pattern, settings] = query_best(url); + if (settings !== undefined && settings.allow) + return {cancel : false}; - if (settings !== undefined && settings.allow) { - console.log("allowing", url); - return {cancel : false}; - } + let nonce = gen_unique(url).substring(1); + let headers = details.responseHeaders.filter(is_noncsp_header); - let nonce = gen_unique(url).substring(1); - let headers = details.responseHeaders.filter(is_noncsp_header); - headers.push({ - name : "content-security-policy", - value : `script-src 'nonce-${nonce}'; script-src-elem 'nonce-${nonce}';` - }); + let rule = `script-src 'nonce-${nonce}';`; + if (is_chrome) + rule += `script-src-elem 'nonce-${nonce}';`; - console.log("modified headers", url, headers); + headers.push({ + name : "content-security-policy", + value : rule + }); - return {responseHeaders: headers}; - } + return {responseHeaders: headers}; +} - async function start() { - storage = await get_storage(); - query_best = await get_query_best(); +async function start_policy_injector() +{ + storage = await get_storage(); + query_best = await get_query_best(); - let extra_opts = ["blocking", "responseHeaders"]; - if (is_chrome) - extra_opts.push("extraHeaders"); + let extra_opts = ["blocking", "responseHeaders"]; + if (is_chrome) + extra_opts.push("extraHeaders"); - browser.webRequest.onHeadersReceived.addListener( - inject, - { - urls: ["<all_urls>"], - types: ["main_frame", "sub_frame"] - }, - extra_opts - ); - } + browser.webRequest.onHeadersReceived.addListener( + inject, + { + urls: ["<all_urls>"], + types: ["main_frame", "sub_frame"] + }, + extra_opts + ); +} - window.start_policy_injector = start; -})(); +/* + * EXPORTS_START + * EXPORT start_policy_injector + * EXPORTS_END + */ diff --git a/background/settings_query.js b/background/settings_query.js index 43538d5..ce01b80 100644 --- a/background/settings_query.js +++ b/background/settings_query.js @@ -5,32 +5,34 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const make_once = window.make_once; - const get_storage = window.get_storage; +/* + * IMPORTS_START + * IMPORT make_once + * IMPORT get_storage + * IMPORT TYPE_PREFIX + * IMPORTS_END + */ - var storage; +var storage; - var exports = {}; +var exports = {}; - async function init(fun) - { - storage = await get_storage(); +async function init(fun) +{ + storage = await get_storage(); - return fun; - } + return fun; +} - // TODO: also support urls with specified ports as well as `data:' urls - function query(url, multiple) - { - let proto_re = "[a-zA-Z]*:\/\/"; - let domain_re = "[^/?#]+"; - let segments_re = "/[^?#]*"; - let query_re = "\\?[^#]*"; +// TODO: also support urls with specified ports +function query(url, multiple) +{ + let proto_re = "[a-zA-Z]*:\/\/"; + let domain_re = "[^/?#]+"; + let segments_re = "/[^?#]*"; + let query_re = "\\?[^#]*"; - let url_regex = new RegExp(`\ + let url_regex = new RegExp(`\ ^\ (${proto_re})\ (${domain_re})\ @@ -39,89 +41,95 @@ #?.*\$\ `); - let regex_match = url_regex.exec(url); - if (regex_match === null) { - console.log("bad url format", url); - return multiple ? [] : [undefined, undefined]; - } + let regex_match = url_regex.exec(url); + if (regex_match === null) { + console.log("bad url format", url); + return multiple ? [] : [undefined, undefined]; + } + + let [_, proto, domain, segments, query] = regex_match; + + domain = domain.split("."); + let segments_trailing_dash = + segments && segments[segments.length - 1] === "/"; + segments = (segments || "").split("/").filter(s => s !== ""); + segments.unshift(""); + + let matched = []; + + for (let d_slice = 0; d_slice < domain.length; d_slice++) { + let domain_part = domain.slice(d_slice).join("."); + let domain_wildcards = []; + if (d_slice === 0) + domain_wildcards.push(""); + if (d_slice === 1) + domain_wildcards.push("*."); + if (d_slice > 0) + domain_wildcards.push("**."); + domain_wildcards.push("***."); + + for (let domain_wildcard of domain_wildcards) { + let domain_pattern = domain_wildcard + domain_part; + + for (let s_slice = segments.length; s_slice > 0; s_slice--) { + let segments_part = segments.slice(0, s_slice).join("/"); + let segments_wildcards = []; + if (s_slice === segments.length) { + if (segments_trailing_dash) + segments_wildcards.push("/"); + segments_wildcards.push(""); + } + if (s_slice === segments.length - 1) { + if (segments[s_slice] !== "*") + segments_wildcards.push("/*"); + } + if (s_slice < segments.length && + (segments[s_slice] !== "**" || + s_slice < segments.length - 1)) + segments_wildcards.push("/**"); + if (segments[s_slice] !== "***" || + s_slice < segments.length) + segments_wildcards.push("/***"); + + for (let segments_wildcard of segments_wildcards) { + let segments_pattern = + segments_part + segments_wildcard; - let [_, proto, domain, segments, query] = regex_match; - - domain = domain.split("."); - let segments_trailing_dash = - segments && segments[segments.length - 1] === "/"; - segments = (segments || "").split("/").filter(s => s !== ""); - segments.unshift(""); - - let matched = []; - - for (let d_slice = 0; d_slice < domain.length; d_slice++) { - let domain_part = domain.slice(d_slice).join("."); - let domain_wildcards = []; - if (d_slice === 0) - domain_wildcards.push(""); - if (d_slice === 1) - domain_wildcards.push("*."); - if (d_slice > 0) - domain_wildcards.push("**."); - domain_wildcards.push("***."); - - for (let domain_wildcard of domain_wildcards) { - let domain_pattern = domain_wildcard + domain_part; - - for (let s_slice = segments.length; s_slice > 0; s_slice--) { - let segments_part = segments.slice(0, s_slice).join("/"); - let segments_wildcards = []; - if (s_slice === segments.length) { - if (segments_trailing_dash) - segments_wildcards.push("/"); - segments_wildcards.push(""); - } - if (s_slice === segments.length - 1) { - if (segments[s_slice] !== "*") - segments_wildcards.push("/*"); - } - if (s_slice < segments.length && - (segments[s_slice] !== "**" || - s_slice < segments.length - 1)) - segments_wildcards.push("/**"); - if (segments[s_slice] !== "***" || - s_slice < segments.length) - segments_wildcards.push("/***"); - - for (let segments_wildcard of segments_wildcards) { - let segments_pattern = - segments_part + segments_wildcard; - - let pattern = proto + domain_pattern + segments_pattern; - console.log("trying", pattern); - let settings = storage.get(TYPE_PREFIX.PAGE, pattern); - - if (settings === undefined) - continue; - - if (!multiple) - return [pattern, settings]; - - matched.push([pattern, settings]); - } + let pattern = proto + domain_pattern + segments_pattern; + console.log("trying", pattern); + let settings = storage.get(TYPE_PREFIX.PAGE, pattern); + + if (settings === undefined) + continue; + + if (!multiple) + return [pattern, settings]; + + matched.push([pattern, settings]); } } } - - return multiple ? matched : [undefined, undefined]; } - function query_best(url) - { - return query(url, false); - } + return multiple ? matched : [undefined, undefined]; +} - function query_all(url) - { - return query(url, true); - } +function query_best(url) +{ + return query(url, false); +} + +function query_all(url) +{ + return query(url, true); +} - window.get_query_best = make_once(() => init(query_best)); - window.get_query_all = make_once(() => init(query_all)); -})(); +const get_query_best = make_once(() => init(query_best)); +const get_query_all = make_once(() => init(query_all)); + +/* + * EXPORTS_START + * EXPORT get_query_best + * EXPORT get_query_all + * EXPORTS_END + */ diff --git a/background/storage.js b/background/storage.js index f3f08c9..48e4e52 100644 --- a/background/storage.js +++ b/background/storage.js @@ -5,390 +5,396 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const TYPE_PREFIX = window.TYPE_PREFIX; - const TYPE_NAME = window.TYPE_NAME; - const list_prefixes = window.list_prefixes; - const make_lock = window.make_lock; - const lock = window.lock; - const unlock = window.unlock; - const make_once = window.make_once; - const browser = window.browser; - const is_chrome = window.is_chrome; - - var exports = {}; - - /* We're yet to decide how to handle errors... */ - - /* Here are some basic wrappers for storage API functions */ - - async function get(key) - { - try { - /* Fix for fact that Chrome does not use promises here */ - let promise = is_chrome ? - new Promise((resolve, reject) => - chrome.storage.local.get(key, - val => resolve(val))) : - browser.storage.local.get(key); - - return (await promise)[key]; - } catch (e) { - console.log(e); - } - } +/* + * IMPORTS_START + * IMPORT TYPE_PREFIX + * IMPORT TYPE_NAME + * IMPORT list_prefixes + * IMPORT make_lock + * IMPORT lock + * IMPORT unlock + * IMPORT make_once + * IMPORT browser + * IMPORT is_chrome + * IMPORTS_END + */ - async function set(key, value) - { - try { - return browser.storage.local.set({[key]: value}); - } catch (e) { - console.log(e); - } - } +var exports = {}; - async function setn(keys_and_values) - { - let obj = Object(); - while (keys_and_values.length > 1) { - let value = keys_and_values.pop(); - let key = keys_and_values.pop(); - obj[key] = value; - } +/* We're yet to decide how to handle errors... */ - try { - return browser.storage.local.set(obj); - } catch (e) { - console.log(e); - } - } +/* Here are some basic wrappers for storage API functions */ - async function set_var(name, value) - { - return set(TYPE_PREFIX.VAR + name, value); - } +async function get(key) +{ + try { + /* Fix for fact that Chrome does not use promises here */ + let promise = is_chrome ? + new Promise((resolve, reject) => + chrome.storage.local.get(key, + val => resolve(val))) : + browser.storage.local.get(key); - async function get_var(name) - { - return get(TYPE_PREFIX.VAR + name); + return (await promise)[key]; + } catch (e) { + console.log(e); + } +} + +async function set(key, value) +{ + try { + return browser.storage.local.set({[key]: value}); + } catch (e) { + console.log(e); + } +} + +async function setn(keys_and_values) +{ + let obj = Object(); + while (keys_and_values.length > 1) { + let value = keys_and_values.pop(); + let key = keys_and_values.pop(); + obj[key] = value; } - /* - * A special case of persisted variable is one that contains list - * of items. - */ - - async function get_list_var(name) - { - let list = await get_var(name); - - return list === undefined ? [] : list; + try { + return browser.storage.local.set(obj); + } catch (e) { + console.log(e); } +} - /* We maintain in-memory copies of some stored lists. */ +async function set_var(name, value) +{ + return set(TYPE_PREFIX.VAR + name, value); +} - async function list(prefix) - { - let name = TYPE_NAME[prefix] + "s"; /* Make plural. */ - let map = new Map(); +async function get_var(name) +{ + return get(TYPE_PREFIX.VAR + name); +} - for (let item of await get_list_var(name)) - map.set(item, await get(prefix + item)); +/* + * A special case of persisted variable is one that contains list + * of items. + */ - return {map, prefix, name, listeners : new Set(), lock : make_lock()}; - } +async function get_list_var(name) +{ + let list = await get_var(name); - var pages; - var bags; - var scripts; + return list === undefined ? [] : list; +} - var list_by_prefix = {}; +/* We maintain in-memory copies of some stored lists. */ - async function init() - { - for (let prefix of list_prefixes) - list_by_prefix[prefix] = await list(prefix); +async function list(prefix) +{ + let name = TYPE_NAME[prefix] + "s"; /* Make plural. */ + let map = new Map(); - return exports; - } + for (let item of await get_list_var(name)) + map.set(item, await get(prefix + item)); - /* - * Facilitate listening to changes - */ + return {map, prefix, name, listeners : new Set(), lock : make_lock()}; +} - exports.add_change_listener = function (cb, prefixes=list_prefixes) - { - if (typeof(prefixes) === "string") - prefixes = [prefixes]; +var pages; +var bags; +var scripts; - for (let prefix of prefixes) - list_by_prefix[prefix].listeners.add(cb); - } +var list_by_prefix = {}; - exports.remove_change_listener = function (cb, prefixes=list_prefixes) - { - if (typeof(prefixes) === "string") - prefixes = [prefixes]; +async function init() +{ + for (let prefix of list_prefixes) + list_by_prefix[prefix] = await list(prefix); - for (let prefix of prefixes) - list_by_prefix[prefix].listeners.delete(cb); - } + return exports; +} - function broadcast_change(change, list) - { - for (let listener_callback of list.listeners) - listener_callback(change); - } +/* + * Facilitate listening to changes + */ - /* Prepare some hepler functions to get elements of a list */ +exports.add_change_listener = function (cb, prefixes=list_prefixes) +{ + if (typeof(prefixes) === "string") + prefixes = [prefixes]; - function list_items_it(list, with_values=false) - { - return with_values ? list.map.entries() : list.map.keys(); - } + for (let prefix of prefixes) + list_by_prefix[prefix].listeners.add(cb); +} - function list_entries_it(list) - { - return list_items_it(list, true); - } +exports.remove_change_listener = function (cb, prefixes=list_prefixes) +{ + if (typeof(prefixes) === "string") + prefixes = [prefixes]; - function list_items(list, with_values=false) - { - let array = []; + for (let prefix of prefixes) + list_by_prefix[prefix].listeners.delete(cb); +} - for (let item of list_items_it(list, with_values)) - array.push(item); +function broadcast_change(change, list) +{ + for (let listener_callback of list.listeners) + listener_callback(change); +} - return array; - } +/* Prepare some hepler functions to get elements of a list */ - function list_entries(list) - { - return list_items(list, true); - } +function list_items_it(list, with_values=false) +{ + return with_values ? list.map.entries() : list.map.keys(); +} - /* - * Below we make additional effort to update map of given kind of items - * every time an item is added/removed to keep everything coherent. - */ - async function set_item(item, value, list) - { - await lock(list.lock); - let result = await _set_item(...arguments); - unlock(list.lock) - return result; - } - async function _set_item(item, value, list) - { - let key = list.prefix + item; - let old_val = list.map.get(item); - if (old_val === undefined) { - let items = list_items(list); - items.push(item); - await setn([key, value, "_" + list.name, items]); - } else { - await set(key, value); - } +function list_entries_it(list) +{ + return list_items_it(list, true); +} - list.map.set(item, value) +function list_items(list, with_values=false) +{ + let array = []; - let change = { - prefix : list.prefix, - item, - old_val, - new_val : value - }; + for (let item of list_items_it(list, with_values)) + array.push(item); - broadcast_change(change, list); + return array; +} - return old_val; - } +function list_entries(list) +{ + return list_items(list, true); +} - // TODO: The actual idea to set value to undefined is good - this way we can - // also set a new list of items in the same API call. But such key - // is still stored in the storage. We need to somehow remove it later. - // For that, we're going to have to store 1 more list of each kind. - async function remove_item(item, list) - { - await lock(list.lock); - let result = await _remove_item(...arguments); - unlock(list.lock) - return result; +/* + * Below we make additional effort to update map of given kind of items + * every time an item is added/removed to keep everything coherent. + */ +async function set_item(item, value, list) +{ + await lock(list.lock); + let result = await _set_item(...arguments); + unlock(list.lock) + return result; +} +async function _set_item(item, value, list) +{ + let key = list.prefix + item; + let old_val = list.map.get(item); + if (old_val === undefined) { + let items = list_items(list); + items.push(item); + await setn([key, value, "_" + list.name, items]); + } else { + await set(key, value); } - async function _remove_item(item, list) - { - let old_val = list.map.get(item); + + list.map.set(item, value) + + let change = { + prefix : list.prefix, + item, + old_val, + new_val : value + }; + + broadcast_change(change, list); + + return old_val; +} + +// TODO: The actual idea to set value to undefined is good - this way we can +// also set a new list of items in the same API call. But such key +// is still stored in the storage. We need to somehow remove it later. +// For that, we're going to have to store 1 more list of each kind. +async function remove_item(item, list) +{ + await lock(list.lock); + let result = await _remove_item(...arguments); + unlock(list.lock) + return result; +} +async function _remove_item(item, list) +{ + let old_val = list.map.get(item); + if (old_val === undefined) + return; + + let key = list.prefix + item; + let items = list_items(list); + let index = items.indexOf(item); + items.splice(index, 1); + + await setn([key, undefined, "_" + list.name, items]); + + list.map.delete(item); + + let change = { + prefix : list.prefix, + item, + old_val, + new_val : undefined + }; + + broadcast_change(change, list); + + return old_val; +} + +// TODO: same as above applies here +async function replace_item(old_item, new_item, list, new_val=undefined) +{ + await lock(list.lock); + let result = await _replace_item(...arguments); + unlock(list.lock) + return result; +} +async function _replace_item(old_item, new_item, list, new_val=undefined) +{ + let old_val = list.map.get(old_item); + if (new_val === undefined) { if (old_val === undefined) return; - - let key = list.prefix + item; - let items = list_items(list); - let index = items.indexOf(item); - items.splice(index, 1); - - await setn([key, undefined, "_" + list.name, items]); - - list.map.delete(item); - - let change = { - prefix : list.prefix, - item, - old_val, - new_val : undefined - }; - - broadcast_change(change, list); - + new_val = old_val + } else if (new_val === old_val && new_item === old_item) { return old_val; } - // TODO: same as above applies here - async function replace_item(old_item, new_item, list, new_val=undefined) - { - await lock(list.lock); - let result = await _replace_item(...arguments); - unlock(list.lock) - return result; + if (old_item === new_item || old_val === undefined) { + await _set_item(new_item, new_val, list); + return old_val; } - async function _replace_item(old_item, new_item, list, new_val=undefined) - { - let old_val = list.map.get(old_item); - if (new_val === undefined) { - if (old_val === undefined) - return; - new_val = old_val - } else if (new_val === old_val && new_item === old_item) { - return old_val; - } - if (old_item === new_item || old_val === undefined) { - await _set_item(new_item, new_val, list); - return old_val; - } + let new_key = list.prefix + new_item; + let old_key = list.prefix + old_item; + let items = list_items(list); + let index = items.indexOf(old_item); + items[index] = new_item; + await setn([old_key, undefined, new_key, new_val, + "_" + list.name, items]); - let new_key = list.prefix + new_item; - let old_key = list.prefix + old_item; - let items = list_items(list); - let index = items.indexOf(old_item); - items[index] = new_item; - await setn([old_key, undefined, new_key, new_val, - "_" + list.name, items]); + list.map.delete(old_item); - list.map.delete(old_item); + let change = { + prefix : list.prefix, + item : old_item, + old_val, + new_val : undefined + }; - let change = { - prefix : list.prefix, - item : old_item, - old_val, - new_val : undefined - }; + broadcast_change(change, list); - broadcast_change(change, list); + list.map.set(new_item, new_val); - list.map.set(new_item, new_val); + change.item = new_item; + change.old_val = undefined; + change.new_val = new_val; - change.item = new_item; - change.old_val = undefined; - change.new_val = new_val; + broadcast_change(change, list); - broadcast_change(change, list); - - return old_val; - } + return old_val; +} - /* - * For scripts, item name is chosen by user, data should be - * an object containing: - * - script's url and hash or - * - script's text or - * - all three - */ - - /* - * For bags, item name is chosen by user, data is an array of 2-element - * arrays with type prefix and script/bag names. - */ - - /* - * For pages data argument is an object with properties `allow' - * and `components'. Item name is url. - */ - - exports.set = async function (prefix, item, data) - { - return set_item(item, data, list_by_prefix[prefix]); - } +/* + * For scripts, item name is chosen by user, data should be + * an object containing: + * - script's url and hash or + * - script's text or + * - all three + */ - exports.get = function (prefix, item) - { - return list_by_prefix[prefix].map.get(item); - } +/* + * For bags, item name is chosen by user, data is an array of 2-element + * arrays with type prefix and script/bag names. + */ - exports.remove = async function (prefix, item) - { - return remove_item(item, list_by_prefix[prefix]); - } +/* + * For pages data argument is an object with properties `allow' + * and `components'. Item name is url. + */ - exports.replace = async function (prefix, old_item, new_item, - new_data=undefined) - { - return replace_item(old_item, new_item, list_by_prefix[prefix], - new_data); - } +exports.set = async function (prefix, item, data) +{ + return set_item(item, data, list_by_prefix[prefix]); +} + +exports.get = function (prefix, item) +{ + return list_by_prefix[prefix].map.get(item); +} + +exports.remove = async function (prefix, item) +{ + return remove_item(item, list_by_prefix[prefix]); +} + +exports.replace = async function (prefix, old_item, new_item, + new_data=undefined) +{ + return replace_item(old_item, new_item, list_by_prefix[prefix], + new_data); +} + +exports.get_all_names = function (prefix) +{ + return list_items(list_by_prefix[prefix]); +} + +exports.get_all_names_it = function (prefix) +{ + return list_items_it(list_by_prefix[prefix]); +} + +exports.get_all = function (prefix) +{ + return list_entries(list_by_prefix[prefix]); +} + +exports.get_all_it = function (prefix) +{ + return list_entries_it(list_by_prefix[prefix]); +} + +/* Finally, a quick way to wipe all the data. */ +// TODO: maybe delete items in such order that none of them ever references +// an already-deleted one? +exports.clear = async function () +{ + let lists = list_prefixes.map((p) => list_by_prefix[p]); + + for (let list of lists) + await lock(list.lock); - exports.get_all_names = function (prefix) - { - return list_items(list_by_prefix[prefix]); - } + for (let list of lists) { - exports.get_all_names_it = function (prefix) - { - return list_items_it(list_by_prefix[prefix]); - } + let change = { + prefix : list.prefix, + new_val : undefined + }; - exports.get_all = function (prefix) - { - return list_entries(list_by_prefix[prefix]); - } + for (let [item, val] of list_entries_it(list)) { + change.item = item; + change.old_val = val; + broadcast_change(change, list); + } - exports.get_all_it = function (prefix) - { - return list_entries_it(list_by_prefix[prefix]); + list.map = new Map(); } - /* Finally, a quick way to wipe all the data. */ - // TODO: maybe delete items in such order that none of them ever references - // an already-deleted one? - exports.clear = async function () - { - let lists = list_prefixes.map((p) => list_by_prefix[p]); - - for (let list of lists) - await lock(list.lock); - - for (let list of lists) { - - let change = { - prefix : list.prefix, - new_val : undefined - }; + await browser.storage.local.clear(); - for (let [item, val] of list_entries_it(list)) { - change.item = item; - change.old_val = val; - broadcast_change(change, list); - } + for (let list of lists) + unlock(list.lock); +} - list.map = new Map(); - } - - await browser.storage.local.clear(); +const get_storage = make_once(init); - for (let list of lists) - unlock(list.lock); - } - - window.get_storage = make_once(init); -})(); +/* + * EXPORTS_START + * EXPORT get_storage + * EXPORTS_END + */ diff --git a/background/storage_server.js b/background/storage_server.js index acdca27..d39898d 100644 --- a/background/storage_server.js +++ b/background/storage_server.js @@ -5,58 +5,63 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const listen_for_connection = window.listen_for_connection; - const get_storage = window.get_storage; - const TYPE_PREFIX = window.TYPE_PREFIX; - const CONNECTION_TYPE = window.CONNECTION_TYPE; - - var storage; - - async function handle_remote_call(port, message) - { - let [call_id, func, args] = message; - - try { - let result = await Promise.resolve(storage[func](...args)); - port.postMessage({call_id, result}); - } catch (error) { - error = error + ''; - port.postMessage({call_id, error}); - } - } +/* + * IMPORTS_START + * IMPORT listen_for_connection + * IMPORT get_storage + * IMPORT TYPE_PREFIX + * IMPORT CONNECTION_TYPE + * IMPORTS_END + */ + +var storage; + +async function handle_remote_call(port, message) +{ + let [call_id, func, args] = message; - function remove_storage_listener(cb) { - storage.remove_change_listener(cb); + try { + let result = await Promise.resolve(storage[func](...args)); + port.postMessage({call_id, result}); + } catch (error) { + error = error + ''; + port.postMessage({call_id, error}); } +} - function new_connection(port) - { - console.log("new remote storage connection!"); +function remove_storage_listener(cb) +{ + storage.remove_change_listener(cb); +} - port.postMessage({ - [TYPE_PREFIX.SCRIPT] : storage.get_all(TYPE_PREFIX.SCRIPT), - [TYPE_PREFIX.BAG] : storage.get_all(TYPE_PREFIX.BAG), - [TYPE_PREFIX.PAGE] : storage.get_all(TYPE_PREFIX.PAGE) - }); +function new_connection(port) +{ + console.log("new remote storage connection!"); - let handle_change = change => port.postMessage(change); + port.postMessage({ + [TYPE_PREFIX.SCRIPT] : storage.get_all(TYPE_PREFIX.SCRIPT), + [TYPE_PREFIX.BAG] : storage.get_all(TYPE_PREFIX.BAG), + [TYPE_PREFIX.PAGE] : storage.get_all(TYPE_PREFIX.PAGE) + }); - storage.add_change_listener(handle_change); + let handle_change = change => port.postMessage(change); - port.onMessage.addListener(m => handle_remote_call(port, m)); - port.onDisconnect.addListener(() => - remove_storage_listener(handle_change)); - } + storage.add_change_listener(handle_change); - async function start() - { - storage = await get_storage(); + port.onMessage.addListener(m => handle_remote_call(port, m)); + port.onDisconnect.addListener(() => + remove_storage_listener(handle_change)); +} - listen_for_connection(CONNECTION_TYPE.REMOTE_STORAGE, new_connection); - } +async function start_storage_server() +{ + storage = await get_storage(); - window.start_storage_server = start; -})(); + listen_for_connection(CONNECTION_TYPE.REMOTE_STORAGE, new_connection); +} + +/* + * EXPORTS_START + * EXPORT start_storage_server + * EXPORTS_END + */ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..efa53f5 --- /dev/null +++ b/build.sh @@ -0,0 +1,289 @@ +#!/bin/sh + +# Copyright (C) 2021 Wojtek Kosior +# Redistribution terms are gathered in the `copyright' file. + +ENDL=" +" + +errcho() { + echo "$@" >&2 +} + +map_set_instr() { + echo "$1__$2='$3'" +} + +map_set() { + eval "$(map_set_instr "$@")" +} + +map_get() { + eval "echo \"\$$1__$2\"" +} + +map_del_instr() { + echo "unset $1__$2" +} + +map_del() { + eval "$(map_del_instr "$@")" +} + +sanitize() { + echo "$1" | tr /.- _ +} + +handle_export_line() { + if [ "x$1" = "xEXPORTS_START" ]; then + if [ "$STATE" = "before_block" ]; then + STATE="in_block" + fi + elif [ "x$1" = "xEXPORT" ]; then + if [ "$STATE" != "in_block" ]; then + return + fi + + EXPORTCODE="${EXPORTCODE}window.killtheweb.$2 = $2;$ENDL" + + PREVIOUS_FILE="$(map_get EXPORTS $2)" + if [ "x$PREVIOUS_FILE" != "x" ]; then + errcho "export $2 present in both $PREVIOUS_FILE and $FILE" + return 1 + fi + + map_set_instr EXPORTS $2 "$FILE" + + elif [ "x$1" = "xEXPORTS_END" ]; then + if [ "$STATE" = "in_block" ]; then + STATE="after_block" + fi + fi +} + +translate_exports() { + STATE="before_block" + EXPORTCODE='' + + while read EXPORT_LINE; do + handle_export_line $EXPORT_LINE || return 1 + done + + map_set_instr EXPORTCODES $FILEKEY "$EXPORTCODE" +} + +add_exports() { + FILE="$1" + FILEKEY="$(sanitize "$FILE")" + + eval "$(grep -o 'EXPORT.\+' "$1" | translate_exports || exit 1)" +} + +handle_import_line() { + if [ "x$1" = "xIMPORTS_START" ]; then + if [ "$STATE" = "before_block" ]; then + STATE="in_block" + fi + elif [ "x$1" = "xIMPORT" ]; then + if [ "$STATE" != "in_block" ]; then + return + fi + + IMPORTCODE="${IMPORTCODE}const $2 = window.killtheweb.$2;$ENDL" + + IMPORTS="$IMPORTS $2" + + elif [ "x$1" = "xIMPORTS_END" ]; then + if [ "$STATE" = "in_block" ]; then + STATE="after_block" + fi + fi +} + +translate_imports() { + STATE="before_block" + IMPORTCODE='' + IMPORTS='' + + while read IMPORT_LINE; do + handle_import_line $IMPORT_LINE || return 1 + done + + map_set_instr IMPORTCODES $FILEKEY "$IMPORTCODE" + map_set_instr IMPORTS $FILEKEY "$IMPORTS" +} + +add_imports() { + FILE="$1" + FILEKEY="$(sanitize "$FILE")" + + eval "$(grep -o 'IMPORT.\+' "$1" | translate_imports || exit 1)" +} + +compute_scripts_list_rec() { + local FILE="$1" + local FILEKEY=$(sanitize "$1") + + local FILESTATE="$(map_get FILESTATES $FILEKEY)" + if [ "xprocessed" = "x$FILESTATE" ]; then + return + fi + if [ "xprocessing" = "x$FILESTATE" ]; then + errcho "import loop on $FILE" + return 1 + fi + + USED="$USED $FILEKEY" + + map_set FILESTATES $FILEKEY "processing" + + local IMPORT + for IMPORT in $(map_get IMPORTS $FILEKEY); do + NEXT_FILE="$(map_get EXPORTS $IMPORT)" + if [ "x" = "x$NEXT_FILE" ]; then + errcho "nothing exports $IMPORT, required by $FILE" + return 1 + fi + if ! compute_scripts_list_rec "$NEXT_FILE"; then + errcho "when satisfying $IMPORT for $FILE" + return 1 + fi + done + + echo $FILE + map_set FILESTATES $FILEKEY "processed" +} + +compute_scripts_list() { + USED='' + echo COMPUTED_SCRIPTS=\"exports_init.js + compute_scripts_list_rec "$1" + echo \" + + for FILEKEY in $USED; do + map_set_instr USED $FILEKEY yes + done +} + +as_json_list() { + while true; do + if [ "x" = "x$2" ]; then + echo -n '\\n'"\t\t\"$1\""'\\n\t' + return + fi + echo -n '\\n'"\t\t\"$1\"," + shift + done +} + +as_html_list() { + while [ "x" != "x$1" ]; do + echo -n '\\n'" <script src=\"/$1\"></script>" + shift + done +} + +set_browser() { + if [ "x$1" = "xmozilla" -o "x$1" = "xchromium" ]; then + BROWSER="$1" + else + errcho "usage: $0 mozilla|chromium" + exit 1 + fi +} + +main() { + set_browser "$1" + + SCRIPTDIRS='background html common content' + + SCRIPTS=$(find $SCRIPTDIRS -name '*.js') + + for SCRIPT in $SCRIPTS; do + add_exports $SCRIPT + add_imports $SCRIPT + done + + eval "$(compute_scripts_list background/main.js || exit 1)" + BGSCRIPTS="$(as_json_list $COMPUTED_SCRIPTS)" + eval "$(compute_scripts_list content/main.js || exit 1)" + CONTENTSCRIPTS="$(as_json_list $COMPUTED_SCRIPTS)" + eval "$(compute_scripts_list html/display-panel.js || exit 1)" + POPUPSCRIPTS="$(as_html_list $COMPUTED_SCRIPTS)" + eval "$(compute_scripts_list html/options_main.js || exit 1)" + OPTIONSSCRIPTS="$(as_html_list $COMPUTED_SCRIPTS)" + + BUILDDIR=build_$BROWSER + rm -rf $BUILDDIR + mkdir $BUILDDIR + for DIR in $(find $SCRIPTDIRS -type d); do + mkdir -p $BUILDDIR/$DIR + done + + CHROMIUM_KEY='' + GECKO_APPLICATIONS='' + + if [ "$BROWSER" = "chromium" ]; then + CHROMIUM_KEY="\n\ +\n\ + // WARNING!!!\n\ + // EACH USER SHOULD REPLACE \"key\" WITH A UNIQUE VALUE!!!\n\ + // OTHERWISE, SECURITY CAN BE TRIVIALLY COMPROMISED!\n\ + //\n\ + // A unique key can be generated with:\n\ + // $ ssh-keygen -f /path/to/new/key.pem -t rsa -b 1024\n\ + //\n\ + // Only relevant to users of chrome-based browsers.\n\ + // Users of Firefox forks are safe.\n\ +\n\ + \"key\": \"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcnNhAAAAAwEAAQAAAIEA+0GT5WNmRRo8e5tL9+BmNtY6aBPwLIgbPnLShYBMSR40iYwLTsccrkwBXb3bs1o4p6q5WJugI8Lsia+GXZc/XHGFkq7D1aWiTxlJLs8z0JC2TQ2/yatYmBMchogYGeeUfP7aI7JJZwpATts+VhIvgga/4FYj+DijMIEpwdckqFEAAAII4Dh7HOA4exwAAAAHc3NoLXJzYQAAAIEA+0GT5WNmRRo8e5tL9+BmNtY6aBPwLIgbPnLShYBMSR40iYwLTsccrkwBXb3bs1o4p6q5WJugI8Lsia+GXZc/XHGFkq7D1aWiTxlJLs8z0JC2TQ2/yatYmBMchogYGeeUfP7aI7JJZwpATts+VhIvgga/4FYj+DijMIEpwdckqFEAAAADAQABAAAAgEHB5/MhEKMFOs8e1cMJ97ZiWubiUPlWpcqyQmauLUj1nspg3JTBh8AWJEVkaxuFgU5gYCHQmRjC6yUdywyziOEkFA4r/WpX4WmbIe+GQHRHhitLN0dgF8N6/fVNOoa5StTdfZqyl23pVXyepoDNjrJFKyupqPMmpwfH5lGr9RwBAAAAQG76HflB/5j8P2YgIYX6dQT4Ei0SqiIjNVy7jFJUQDKSJg/PYkedE02JZJBJPcMYxEJUxXtMgq+upamNILfkmY0AAABBAP4v0O5dqjy16xDDFzb4DPNAcw5Za9KJaXKVkUuKXMNZOKTR0RC/upjNTmttY980RKdIx5zA25dO8cx563bSDIsAAABBAP0MaOpBiai/eRmLqhlthHODa+Mur6W3uc9PyhWhgDBjLNMR/doaYeyfVKxtIiN3a+HkN++G+vbokRweQv++bhMAAAANdXJ6QGxvY2FsaG9zdAECAwQFBg==\"," + else + GECKO_APPLICATIONS="\n\ + \"applications\": {\n\ + \"gecko\": {\n\ + \"id\": \"{6fe13369-88e9-440f-b837-5012fb3bedec}\",\n\ + \"strict_min_version\": \"60.0\"\n\ + }\n\ + }," + fi + + sed "\ +s^_GECKO_APPLICATIONS_^$GECKO_APPLICATIONS^ +s^_CHROMIUM_KEY_^$CHROMIUM_KEY^ +s^_BGSCRIPTS_^$BGSCRIPTS^ +s^_CONTENTSCRIPTS_^$CONTENTSCRIPTS^" \ + < manifest.json > $BUILDDIR/manifest.json + + sed "s^_POPUPSCRIPTS_^$POPUPSCRIPTS^" \ + < html/display-panel.html > $BUILDDIR/html/display-panel.html + + sed "s^_OPTIONSSCRIPTS_^$OPTIONSSCRIPTS^" \ + < html/options.html > $BUILDDIR/html/options.html + + for FILE in $SCRIPTS; do + FILEKEY=$(sanitize "$FILE") + if [ "xyes" != "x$(map_get USED $FILEKEY)" ]; then + errcho "WARNING! $FILE not used" + else + (echo "\ +\"use strict\"; + +(() => { +$(map_get IMPORTCODES $FILEKEY) + +"; cat $FILE; echo " + +$(map_get EXPORTCODES $FILEKEY) +})();") > $BUILDDIR/$FILE + fi + done + + echo "\ +window.killtheweb={}; +window.browser = this.browser; /* fix for stupid Firefox */ +" > $BUILDDIR/exports_init.js + + cp -r icons/ copyright licenses/ $BUILDDIR +} + +main "$@" diff --git a/common/browser.js b/common/browser.js index 0ff3510..e50a121 100644 --- a/common/browser.js +++ b/common/browser.js @@ -5,21 +5,19 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - /* * This module normalizes access to WebExtension apis between * chrome-based and firefox-based browsers. */ -(() => { - if (typeof browser === "object") { - window.browser = browser; - window.is_chrome = false; - window.is_mozilla = true; - } else { - window.browser = window.chrome; - window.is_chrome = true; - window.is_mozilla = false; - } -})(); +const is_mozilla = typeof window.browser === "object"; +const is_chrome = !is_mozilla; +const browser = window[is_chrome ? "chrome" : "browser"]; + +/* + * EXPORTS_START + * EXPORT browser + * EXPORT is_chrome + * EXPORT is_mozilla + * EXPORTS_END + */ diff --git a/common/connection_types.js b/common/connection_types.js index cc3c976..e227532 100644 --- a/common/connection_types.js +++ b/common/connection_types.js @@ -5,18 +5,18 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - /* * Those need to be strings so they can be used as 'name' parameter * to browser.runtime.connect() */ -(() => { - const CONNECTION_TYPE = { - REMOTE_STORAGE : "0", - PAGE_ACTIONS : "1" - }; +const CONNECTION_TYPE = { + REMOTE_STORAGE : "0", + PAGE_ACTIONS : "1" +}; - window.CONNECTION_TYPE = CONNECTION_TYPE; -})(); +/* + * EXPORTS_START + * EXPORT CONNECTION_TYPE + * EXPORTS_END + */ diff --git a/common/gen_unique.js b/common/gen_unique.js index 4bcfd67..7ec8b4a 100644 --- a/common/gen_unique.js +++ b/common/gen_unique.js @@ -5,25 +5,29 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const sha256 = window.sha256; - const browser = window.browser; - const is_chrome = window.is_chrome; +/* + * IMPORTS_START + * IMPORT sha256 + * IMPORT browser + * IMPORT is_chrome + * IMPORTS_END + */ - function get_id() - { - if (is_chrome) - return browser.runtime.getManifest().key.substring(0, 50); - else - return browser.runtime.getURL("dummy"); - } +function get_id() +{ + if (is_chrome) + return browser.runtime.getManifest().key.substring(0, 50); + else + return browser.runtime.getURL("dummy"); +} - function gen_unique(url) - { - return "#" + sha256(get_id() + url); - } +function gen_unique(url) +{ + return "#" + sha256(get_id() + url); +} - window.gen_unique = gen_unique; -})(); +/* + * EXPORTS_START + * EXPORT gen_unique + * EXPORTS_END + */ diff --git a/common/lock.js b/common/lock.js index 3358393..1130762 100644 --- a/common/lock.js +++ b/common/lock.js @@ -19,40 +19,40 @@ * in a promise. */ -"use strict"; - -(() => { - function make_lock() { - return {free: true, queue: []}; +function make_lock() { + return {free: true, queue: []}; +} + +function _lock(lock, cb) { + if (lock.free) { + lock.free = false; + setTimeout(cb); + } else { + lock.queue.push(cb); } - - function _lock(lock, cb) { - if (lock.free) { - lock.free = false; - setTimeout(cb); - } else { - lock.queue.push(cb); - } +} + +function lock(lock) { + return new Promise((resolve, reject) => _lock(lock, resolve)); +} + +function unlock(lock) { + if (lock.free) + throw new Exception("Attempting to release a free lock"); + + if (lock.queue.length === 0) { + lock.free = true; + } else { + let cb = lock.queue[0]; + lock.queue.splice(0, 1); + setTimeout(cb); } +} - function lock(lock) { - return new Promise((resolve, reject) => _lock(lock, resolve)); - } - - function unlock(lock) { - if (lock.free) - throw new Exception("Attempting to release a free lock"); - - if (lock.queue.length === 0) { - lock.free = true; - } else { - let cb = lock.queue[0]; - lock.queue.splice(0, 1); - setTimeout(cb); - } - } - - window.make_lock = make_lock; - window.lock = lock; - window.unlock = unlock; -})(); +/* + * EXPORTS_START + * EXPORT make_lock + * EXPORT lock + * EXPORT unlock + * EXPORTS_END + */ diff --git a/common/once.js b/common/once.js index d56b3c8..e3e6dbe 100644 --- a/common/once.js +++ b/common/once.js @@ -5,37 +5,37 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - /* * This module provides an easy way to wrap an async function into a promise * so that it only gets executed once. */ -(() => { - async function assign_result(state, result_producer) - { - state.result = await result_producer(); - state.ready = true; - for (let cb of state.waiting) - setTimeout(cb, 0, state.result); - state.waiting = undefined; - } +async function assign_result(state, result_producer) +{ + state.result = await result_producer(); + state.ready = true; + for (let cb of state.waiting) + setTimeout(cb, 0, state.result); + state.waiting = undefined; +} - async function get_result(state) - { - if (state.ready) - return state.result; +async function get_result(state) +{ + if (state.ready) + return state.result; - return new Promise((resolve, reject) => state.waiting.push(resolve)); - } + return new Promise((resolve, reject) => state.waiting.push(resolve)); +} - function make_once(result_producer) - { - let state = {waiting : [], ready : false, result : undefined}; - assign_result(state, result_producer); - return () => get_result(state); - } +function make_once(result_producer) +{ + let state = {waiting : [], ready : false, result : undefined}; + assign_result(state, result_producer); + return () => get_result(state); +} - window.make_once = make_once; -})(); +/* + * EXPORTS_START + * EXPORT make_once + * EXPORTS_END + */ diff --git a/common/sha256.js b/common/sha256.js index 271ec87..4fa07b5 100644 --- a/common/sha256.js +++ b/common/sha256.js @@ -7,32 +7,28 @@ * @license MIT */ -"use strict"; +var fake_window = {}; -(() => { - var fake_window = {}; - - console.log('hello, crypto!'); - var ERROR = 'input is invalid type'; - var WINDOW = typeof window === 'object'; - var root = /*WINDOW ? window : {}*/ fake_window; - if (root.JS_SHA256_NO_WINDOW) { +var ERROR = 'input is invalid type'; +var WINDOW = typeof fake_window === 'object'; +var root = /*WINDOW ? window : {}*/ fake_window; +if (root.JS_SHA256_NO_WINDOW) { WINDOW = false; - } - var WEB_WORKER = !WINDOW && typeof self === 'object'; - var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; - if (NODE_JS) { +} +var WEB_WORKER = !WINDOW && typeof self === 'object'; +var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; +if (NODE_JS) { root = global; - } else if (WEB_WORKER) { +} else if (WEB_WORKER) { root = self; - } - var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports; - var AMD = typeof define === 'function' && define.amd; - var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; - var HEX_CHARS = '0123456789abcdef'.split(''); - var EXTRA = [-2147483648, 8388608, 32768, 128]; - var SHIFT = [24, 16, 8, 0]; - var K = [ +} +var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports; +var AMD = typeof define === 'function' && define.amd; +var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; +var HEX_CHARS = '0123456789abcdef'.split(''); +var EXTRA = [-2147483648, 8388608, 32768, 128]; +var SHIFT = [24, 16, 8, 0]; +var K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, @@ -41,209 +37,209 @@ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ]; - var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; +]; +var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; - var blocks = []; +var blocks = []; - if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { +if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { Array.isArray = function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; + return Object.prototype.toString.call(obj) === '[object Array]'; }; - } +} - if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { +if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { ArrayBuffer.isView = function (obj) { - return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; + return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; }; - } +} - var createOutputMethod = function (outputType, is224) { +var createOutputMethod = function (outputType, is224) { return function (message) { - return new Sha256(is224, true).update(message)[outputType](); + return new Sha256(is224, true).update(message)[outputType](); }; - }; +}; - var createMethod = function (is224) { +var createMethod = function (is224) { var method = createOutputMethod('hex', is224); if (NODE_JS) { - method = nodeWrap(method, is224); + method = nodeWrap(method, is224); } method.create = function () { - return new Sha256(is224); + return new Sha256(is224); }; method.update = function (message) { - return method.create().update(message); + return method.create().update(message); }; for (var i = 0; i < OUTPUT_TYPES.length; ++i) { - var type = OUTPUT_TYPES[i]; - method[type] = createOutputMethod(type, is224); + var type = OUTPUT_TYPES[i]; + method[type] = createOutputMethod(type, is224); } return method; - }; +}; - var nodeWrap = function (method, is224) { +var nodeWrap = function (method, is224) { var crypto = eval("require('crypto')"); var Buffer = eval("require('buffer').Buffer"); var algorithm = is224 ? 'sha224' : 'sha256'; var nodeMethod = function (message) { - if (typeof message === 'string') { - return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); - } else { - if (message === null || message === undefined) { - throw new Error(ERROR); - } else if (message.constructor === ArrayBuffer) { - message = new Uint8Array(message); - } - } - if (Array.isArray(message) || ArrayBuffer.isView(message) || - message.constructor === Buffer) { - return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); - } else { - return method(message); - } + if (typeof message === 'string') { + return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); + } else { + if (message === null || message === undefined) { + throw new Error(ERROR); + } else if (message.constructor === ArrayBuffer) { + message = new Uint8Array(message); + } + } + if (Array.isArray(message) || ArrayBuffer.isView(message) || + message.constructor === Buffer) { + return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); + } else { + return method(message); + } }; return nodeMethod; - }; +}; - var createHmacOutputMethod = function (outputType, is224) { +var createHmacOutputMethod = function (outputType, is224) { return function (key, message) { - return new HmacSha256(key, is224, true).update(message)[outputType](); + return new HmacSha256(key, is224, true).update(message)[outputType](); }; - }; +}; - var createHmacMethod = function (is224) { +var createHmacMethod = function (is224) { var method = createHmacOutputMethod('hex', is224); method.create = function (key) { - return new HmacSha256(key, is224); + return new HmacSha256(key, is224); }; method.update = function (key, message) { - return method.create(key).update(message); + return method.create(key).update(message); }; for (var i = 0; i < OUTPUT_TYPES.length; ++i) { - var type = OUTPUT_TYPES[i]; - method[type] = createHmacOutputMethod(type, is224); + var type = OUTPUT_TYPES[i]; + method[type] = createHmacOutputMethod(type, is224); } return method; - }; +}; - function Sha256(is224, sharedMemory) { +function Sha256(is224, sharedMemory) { if (sharedMemory) { - blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - this.blocks = blocks; + blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = + blocks[4] = blocks[5] = blocks[6] = blocks[7] = + blocks[8] = blocks[9] = blocks[10] = blocks[11] = + blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + this.blocks = blocks; } else { - this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } if (is224) { - this.h0 = 0xc1059ed8; - this.h1 = 0x367cd507; - this.h2 = 0x3070dd17; - this.h3 = 0xf70e5939; - this.h4 = 0xffc00b31; - this.h5 = 0x68581511; - this.h6 = 0x64f98fa7; - this.h7 = 0xbefa4fa4; + this.h0 = 0xc1059ed8; + this.h1 = 0x367cd507; + this.h2 = 0x3070dd17; + this.h3 = 0xf70e5939; + this.h4 = 0xffc00b31; + this.h5 = 0x68581511; + this.h6 = 0x64f98fa7; + this.h7 = 0xbefa4fa4; } else { // 256 - this.h0 = 0x6a09e667; - this.h1 = 0xbb67ae85; - this.h2 = 0x3c6ef372; - this.h3 = 0xa54ff53a; - this.h4 = 0x510e527f; - this.h5 = 0x9b05688c; - this.h6 = 0x1f83d9ab; - this.h7 = 0x5be0cd19; + this.h0 = 0x6a09e667; + this.h1 = 0xbb67ae85; + this.h2 = 0x3c6ef372; + this.h3 = 0xa54ff53a; + this.h4 = 0x510e527f; + this.h5 = 0x9b05688c; + this.h6 = 0x1f83d9ab; + this.h7 = 0x5be0cd19; } this.block = this.start = this.bytes = this.hBytes = 0; this.finalized = this.hashed = false; this.first = true; this.is224 = is224; - } +} - Sha256.prototype.update = function (message) { +Sha256.prototype.update = function (message) { if (this.finalized) { - return; + return; } var notString, type = typeof message; if (type !== 'string') { - if (type === 'object') { - if (message === null) { - throw new Error(ERROR); - } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { - message = new Uint8Array(message); - } else if (!Array.isArray(message)) { - if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { + if (type === 'object') { + if (message === null) { + throw new Error(ERROR); + } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { + message = new Uint8Array(message); + } else if (!Array.isArray(message)) { + if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { + throw new Error(ERROR); + } + } + } else { throw new Error(ERROR); - } - } - } else { - throw new Error(ERROR); - } - notString = true; + } + notString = true; } var code, index = 0, i, length = message.length, blocks = this.blocks; while (index < length) { - if (this.hashed) { - this.hashed = false; - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } - - if (notString) { - for (i = this.start; index < length && i < 64; ++index) { - blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; - } - } else { - for (i = this.start; index < length && i < 64; ++index) { - code = message.charCodeAt(index); - if (code < 0x80) { - blocks[i >> 2] |= code << SHIFT[i++ & 3]; - } else if (code < 0x800) { - blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else if (code < 0xd800 || code >= 0xe000) { - blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); - blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } - } - } - - this.lastByteIndex = i; - this.bytes += i - this.start; - if (i >= 64) { - this.block = blocks[16]; - this.start = i - 64; - this.hash(); - this.hashed = true; - } else { - this.start = i; - } + if (this.hashed) { + this.hashed = false; + blocks[0] = this.block; + blocks[16] = blocks[1] = blocks[2] = blocks[3] = + blocks[4] = blocks[5] = blocks[6] = blocks[7] = + blocks[8] = blocks[9] = blocks[10] = blocks[11] = + blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + } + + if (notString) { + for (i = this.start; index < length && i < 64; ++index) { + blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; + } + } else { + for (i = this.start; index < length && i < 64; ++index) { + code = message.charCodeAt(index); + if (code < 0x80) { + blocks[i >> 2] |= code << SHIFT[i++ & 3]; + } else if (code < 0x800) { + blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } else if (code < 0xd800 || code >= 0xe000) { + blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } else { + code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); + blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; + blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; + } + } + } + + this.lastByteIndex = i; + this.bytes += i - this.start; + if (i >= 64) { + this.block = blocks[16]; + this.start = i - 64; + this.hash(); + this.hashed = true; + } else { + this.start = i; + } } if (this.bytes > 4294967295) { - this.hBytes += this.bytes / 4294967296 << 0; - this.bytes = this.bytes % 4294967296; + this.hBytes += this.bytes / 4294967296 << 0; + this.bytes = this.bytes % 4294967296; } return this; - }; +}; - Sha256.prototype.finalize = function () { +Sha256.prototype.finalize = function () { if (this.finalized) { - return; + return; } this.finalized = true; var blocks = this.blocks, i = this.lastByteIndex; @@ -251,86 +247,86 @@ blocks[i >> 2] |= EXTRA[i & 3]; this.block = blocks[16]; if (i >= 56) { - if (!this.hashed) { - this.hash(); - } - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + if (!this.hashed) { + this.hash(); + } + blocks[0] = this.block; + blocks[16] = blocks[1] = blocks[2] = blocks[3] = + blocks[4] = blocks[5] = blocks[6] = blocks[7] = + blocks[8] = blocks[9] = blocks[10] = blocks[11] = + blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; } blocks[14] = this.hBytes << 3 | this.bytes >>> 29; blocks[15] = this.bytes << 3; this.hash(); - }; +}; - Sha256.prototype.hash = function () { +Sha256.prototype.hash = function () { var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, - h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; + h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; for (j = 16; j < 64; ++j) { - // rightrotate - t1 = blocks[j - 15]; - s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); - t1 = blocks[j - 2]; - s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); - blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; + // rightrotate + t1 = blocks[j - 15]; + s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); + t1 = blocks[j - 2]; + s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); + blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; } bc = b & c; for (j = 0; j < 64; j += 4) { - if (this.first) { - if (this.is224) { - ab = 300032; - t1 = blocks[0] - 1413257819; - h = t1 - 150054599 << 0; - d = t1 + 24177077 << 0; - } else { - ab = 704751109; - t1 = blocks[0] - 210244248; - h = t1 - 1521486534 << 0; - d = t1 + 143694565 << 0; - } - this.first = false; - } else { - s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); - s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); - ab = a & b; - maj = ab ^ (a & c) ^ bc; - ch = (e & f) ^ (~e & g); - t1 = h + s1 + ch + K[j] + blocks[j]; - t2 = s0 + maj; - h = d + t1 << 0; - d = t1 + t2 << 0; - } - s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); - s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); - da = d & a; - maj = da ^ (d & b) ^ ab; - ch = (h & e) ^ (~h & f); - t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; - t2 = s0 + maj; - g = c + t1 << 0; - c = t1 + t2 << 0; - s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); - s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); - cd = c & d; - maj = cd ^ (c & a) ^ da; - ch = (g & h) ^ (~g & e); - t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; - t2 = s0 + maj; - f = b + t1 << 0; - b = t1 + t2 << 0; - s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); - s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); - bc = b & c; - maj = bc ^ (b & d) ^ cd; - ch = (f & g) ^ (~f & h); - t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; - t2 = s0 + maj; - e = a + t1 << 0; - a = t1 + t2 << 0; + if (this.first) { + if (this.is224) { + ab = 300032; + t1 = blocks[0] - 1413257819; + h = t1 - 150054599 << 0; + d = t1 + 24177077 << 0; + } else { + ab = 704751109; + t1 = blocks[0] - 210244248; + h = t1 - 1521486534 << 0; + d = t1 + 143694565 << 0; + } + this.first = false; + } else { + s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); + s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); + ab = a & b; + maj = ab ^ (a & c) ^ bc; + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + K[j] + blocks[j]; + t2 = s0 + maj; + h = d + t1 << 0; + d = t1 + t2 << 0; + } + s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); + s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); + da = d & a; + maj = da ^ (d & b) ^ ab; + ch = (h & e) ^ (~h & f); + t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; + t2 = s0 + maj; + g = c + t1 << 0; + c = t1 + t2 << 0; + s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); + s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); + cd = c & d; + maj = cd ^ (c & a) ^ da; + ch = (g & h) ^ (~g & e); + t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; + t2 = s0 + maj; + f = b + t1 << 0; + b = t1 + t2 << 0; + s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); + s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); + bc = b & c; + maj = bc ^ (b & d) ^ cd; + ch = (f & g) ^ (~f & h); + t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; + t2 = s0 + maj; + e = a + t1 << 0; + a = t1 + t2 << 0; } this.h0 = this.h0 + a << 0; @@ -341,77 +337,77 @@ this.h5 = this.h5 + f << 0; this.h6 = this.h6 + g << 0; this.h7 = this.h7 + h << 0; - }; +}; - Sha256.prototype.hex = function () { +Sha256.prototype.hex = function () { this.finalize(); var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, - h6 = this.h6, h7 = this.h7; + h6 = this.h6, h7 = this.h7; var hex = HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + - HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + - HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + - HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + - HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + - HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + - HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + - HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + - HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + - HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + - HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + - HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + - HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + - HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + - HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + - HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + - HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + - HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + - HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + - HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] + - HEX_CHARS[(h5 >> 28) & 0x0F] + HEX_CHARS[(h5 >> 24) & 0x0F] + - HEX_CHARS[(h5 >> 20) & 0x0F] + HEX_CHARS[(h5 >> 16) & 0x0F] + - HEX_CHARS[(h5 >> 12) & 0x0F] + HEX_CHARS[(h5 >> 8) & 0x0F] + - HEX_CHARS[(h5 >> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] + - HEX_CHARS[(h6 >> 28) & 0x0F] + HEX_CHARS[(h6 >> 24) & 0x0F] + - HEX_CHARS[(h6 >> 20) & 0x0F] + HEX_CHARS[(h6 >> 16) & 0x0F] + - HEX_CHARS[(h6 >> 12) & 0x0F] + HEX_CHARS[(h6 >> 8) & 0x0F] + - HEX_CHARS[(h6 >> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F]; + HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + + HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + + HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + + HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + + HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + + HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + + HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + + HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + + HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + + HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + + HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + + HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + + HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + + HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + + HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + + HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + + HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + + HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + + HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] + + HEX_CHARS[(h5 >> 28) & 0x0F] + HEX_CHARS[(h5 >> 24) & 0x0F] + + HEX_CHARS[(h5 >> 20) & 0x0F] + HEX_CHARS[(h5 >> 16) & 0x0F] + + HEX_CHARS[(h5 >> 12) & 0x0F] + HEX_CHARS[(h5 >> 8) & 0x0F] + + HEX_CHARS[(h5 >> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] + + HEX_CHARS[(h6 >> 28) & 0x0F] + HEX_CHARS[(h6 >> 24) & 0x0F] + + HEX_CHARS[(h6 >> 20) & 0x0F] + HEX_CHARS[(h6 >> 16) & 0x0F] + + HEX_CHARS[(h6 >> 12) & 0x0F] + HEX_CHARS[(h6 >> 8) & 0x0F] + + HEX_CHARS[(h6 >> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F]; if (!this.is224) { - hex += HEX_CHARS[(h7 >> 28) & 0x0F] + HEX_CHARS[(h7 >> 24) & 0x0F] + - HEX_CHARS[(h7 >> 20) & 0x0F] + HEX_CHARS[(h7 >> 16) & 0x0F] + - HEX_CHARS[(h7 >> 12) & 0x0F] + HEX_CHARS[(h7 >> 8) & 0x0F] + - HEX_CHARS[(h7 >> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F]; + hex += HEX_CHARS[(h7 >> 28) & 0x0F] + HEX_CHARS[(h7 >> 24) & 0x0F] + + HEX_CHARS[(h7 >> 20) & 0x0F] + HEX_CHARS[(h7 >> 16) & 0x0F] + + HEX_CHARS[(h7 >> 12) & 0x0F] + HEX_CHARS[(h7 >> 8) & 0x0F] + + HEX_CHARS[(h7 >> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F]; } return hex; - }; +}; - Sha256.prototype.toString = Sha256.prototype.hex; +Sha256.prototype.toString = Sha256.prototype.hex; - Sha256.prototype.digest = function () { +Sha256.prototype.digest = function () { this.finalize(); var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, - h6 = this.h6, h7 = this.h7; + h6 = this.h6, h7 = this.h7; var arr = [ - (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, - (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, - (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, - (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, - (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, - (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, - (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF + (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, + (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, + (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, + (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, + (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, + (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, + (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF ]; if (!this.is224) { - arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); + arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); } return arr; - }; +}; - Sha256.prototype.array = Sha256.prototype.digest; +Sha256.prototype.array = Sha256.prototype.digest; - Sha256.prototype.arrayBuffer = function () { +Sha256.prototype.arrayBuffer = function () { this.finalize(); var buffer = new ArrayBuffer(this.is224 ? 28 : 32); @@ -424,60 +420,60 @@ dataView.setUint32(20, this.h5); dataView.setUint32(24, this.h6); if (!this.is224) { - dataView.setUint32(28, this.h7); + dataView.setUint32(28, this.h7); } return buffer; - }; +}; - function HmacSha256(key, is224, sharedMemory) { +function HmacSha256(key, is224, sharedMemory) { var i, type = typeof key; if (type === 'string') { - var bytes = [], length = key.length, index = 0, code; - for (i = 0; i < length; ++i) { - code = key.charCodeAt(i); - if (code < 0x80) { - bytes[index++] = code; - } else if (code < 0x800) { - bytes[index++] = (0xc0 | (code >> 6)); - bytes[index++] = (0x80 | (code & 0x3f)); - } else if (code < 0xd800 || code >= 0xe000) { - bytes[index++] = (0xe0 | (code >> 12)); - bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); - bytes[index++] = (0x80 | (code & 0x3f)); - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff)); - bytes[index++] = (0xf0 | (code >> 18)); - bytes[index++] = (0x80 | ((code >> 12) & 0x3f)); - bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); - bytes[index++] = (0x80 | (code & 0x3f)); - } - } - key = bytes; + var bytes = [], length = key.length, index = 0, code; + for (i = 0; i < length; ++i) { + code = key.charCodeAt(i); + if (code < 0x80) { + bytes[index++] = code; + } else if (code < 0x800) { + bytes[index++] = (0xc0 | (code >> 6)); + bytes[index++] = (0x80 | (code & 0x3f)); + } else if (code < 0xd800 || code >= 0xe000) { + bytes[index++] = (0xe0 | (code >> 12)); + bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); + bytes[index++] = (0x80 | (code & 0x3f)); + } else { + code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff)); + bytes[index++] = (0xf0 | (code >> 18)); + bytes[index++] = (0x80 | ((code >> 12) & 0x3f)); + bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); + bytes[index++] = (0x80 | (code & 0x3f)); + } + } + key = bytes; } else { - if (type === 'object') { - if (key === null) { - throw new Error(ERROR); - } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { - key = new Uint8Array(key); - } else if (!Array.isArray(key)) { - if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { + if (type === 'object') { + if (key === null) { + throw new Error(ERROR); + } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { + key = new Uint8Array(key); + } else if (!Array.isArray(key)) { + if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { + throw new Error(ERROR); + } + } + } else { throw new Error(ERROR); - } - } - } else { - throw new Error(ERROR); - } + } } if (key.length > 64) { - key = (new Sha256(is224, true)).update(key).array(); + key = (new Sha256(is224, true)).update(key).array(); } var oKeyPad = [], iKeyPad = []; for (i = 0; i < 64; ++i) { - var b = key[i] || 0; - oKeyPad[i] = 0x5c ^ b; - iKeyPad[i] = 0x36 ^ b; + var b = key[i] || 0; + oKeyPad[i] = 0x5c ^ b; + iKeyPad[i] = 0x36 ^ b; } Sha256.call(this, is224, sharedMemory); @@ -486,38 +482,43 @@ this.oKeyPad = oKeyPad; this.inner = true; this.sharedMemory = sharedMemory; - } - HmacSha256.prototype = new Sha256(); +} +HmacSha256.prototype = new Sha256(); - HmacSha256.prototype.finalize = function () { +HmacSha256.prototype.finalize = function () { Sha256.prototype.finalize.call(this); if (this.inner) { - this.inner = false; - var innerHash = this.array(); - Sha256.call(this, this.is224, this.sharedMemory); - this.update(this.oKeyPad); - this.update(innerHash); - Sha256.prototype.finalize.call(this); + this.inner = false; + var innerHash = this.array(); + Sha256.call(this, this.is224, this.sharedMemory); + this.update(this.oKeyPad); + this.update(innerHash); + Sha256.prototype.finalize.call(this); } - }; +}; - var exports = createMethod(); - exports.sha256 = exports; - exports.sha224 = createMethod(true); - exports.sha256.hmac = createHmacMethod(); - exports.sha224.hmac = createHmacMethod(true); +var exports = createMethod(); +exports.sha256 = exports; +exports.sha224 = createMethod(true); +exports.sha256.hmac = createHmacMethod(); +exports.sha224.hmac = createHmacMethod(true); - if (COMMON_JS) { +if (COMMON_JS) { module.exports = exports; - } else { +} else { root.sha256 = exports.sha256; root.sha224 = exports.sha224; if (AMD) { - define(function () { - return exports; - }); + define(function () { + return exports; + }); } - } +} + +const sha256 = fake_window.sha256; - window.sha256 = fake_window.sha256; -})(); +/* + * EXPORTS_START + * EXPORT sha256 + * EXPORTS_END + */ diff --git a/common/storage_client.js b/common/storage_client.js index 1bc9a88..2bbddb6 100644 --- a/common/storage_client.js +++ b/common/storage_client.js @@ -5,182 +5,188 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const CONNECTION_TYPE = window.CONNECTION_TYPE; - const TYPE_PREFIX = window.TYPE_PREFIX; - const list_prefixes = window.list_prefixes; - const make_once = window.make_once; - const browser = window.browser; - - var call_id = 0; - var port; - var calls_waiting = new Map(); - - function set_call_callback(resolve, reject, func, args) - { - port.postMessage([call_id, func, args]); - calls_waiting.set(call_id++, [resolve, reject]); - } - - async function remote_call(func, args) - { - return new Promise((resolve, reject) => - set_call_callback(resolve, reject, func, args)); - } - - function handle_message(message) - { - let callbacks = calls_waiting.get(message.call_id); - if (callbacks === undefined) { - handle_change(message); - return; - } - - let [resolve, reject] = callbacks; - calls_waiting.delete(message.call_id); - if (message.error !== undefined) - setTimeout(reject, 0, message.error); - else - setTimeout(resolve, 0, message.result); - } - - function list(name, prefix) - { - return {prefix, name, listeners : new Set()}; - } - - var scripts = list("scripts", TYPE_PREFIX.SCRIPT); - var bags = list("bags", TYPE_PREFIX.BAG); - var pages = list("pages", TYPE_PREFIX.PAGE); - - const list_by_prefix = { - [TYPE_PREFIX.SCRIPT] : scripts, - [TYPE_PREFIX.BAG] : bags, - [TYPE_PREFIX.PAGE] : pages - }; - - var resolve_init; - - function handle_first_message(message) - { - for (let prefix of Object.keys(message)) - list_by_prefix[prefix].map = new Map(message[prefix]); - - port.onMessage.removeListener(handle_first_message); - port.onMessage.addListener(handle_message); - - resolve_init(); - } - - function handle_change(change) - { - let list = list_by_prefix[change.prefix]; - - if (change.new_val === undefined) - list.map.delete(change.item); - else - list.map.set(change.item, change.new_val); - - for (let listener_callback of list.listeners) - listener_callback(change); - } - - var exports = {}; - - function start_connection(resolve) - { - resolve_init = resolve; - port = browser.runtime.connect({name : CONNECTION_TYPE.REMOTE_STORAGE}); - port.onMessage.addListener(handle_first_message); - } - - async function init() { - await new Promise((resolve, reject) => start_connection(resolve)); - return exports; - } - - for (let call_name of ["set", "remove", "replace", "clear"]) - exports [call_name] = (...args) => remote_call(call_name, args); - - // TODO: Much of the code below is copy-pasted from /background/storage.mjs. - // This should later be refactored into a separate module - // to avoid duplication. - - /* - * Facilitate listening to changes - */ - - exports.add_change_listener = function (cb, prefixes=list_prefixes) - { - if (typeof(prefixes) === "string") - prefixes = [prefixes]; - - for (let prefix of prefixes) - list_by_prefix[prefix].listeners.add(cb); - } - - exports.remove_change_listener = function (cb, prefixes=list_prefixes) - { - if (typeof(prefixes) === "string") - prefixes = [prefixes]; - - for (let prefix of prefixes) - list_by_prefix[prefix].listeners.delete(cb); - } - - /* Prepare some hepler functions to get elements of a list */ - - function list_items_it(list, with_values=false) - { - return with_values ? list.map.entries() : list.map.keys(); - } - - function list_entries_it(list) - { - return list_items_it(list, true); - } - - function list_items(list, with_values=false) - { - let array = []; - - for (let item of list_items_it(list, with_values)) - array.push(item); - - return array; - } - - function list_entries(list) - { - return list_items(list, true); - } - - exports.get = function (prefix, item) - { - return list_by_prefix[prefix].map.get(item); - } - - exports.get_all_names = function (prefix) - { - return list_items(list_by_prefix[prefix]); - } - - exports.get_all_names_it = function (prefix) - { - return list_items_it(list_by_prefix[prefix]); - } - - exports.get_all = function (prefix) - { - return list_entries(list_by_prefix[prefix]); - } +/* + * IMPORTS_START + * IMPORT CONNECTION_TYPE + * IMPORT TYPE_PREFIX + * IMPORT list_prefixes + * IMPORT make_once + * IMPORT browser + * IMPORTS_END + */ - exports.get_all_it = function (prefix) - { - return list_entries_it(list_by_prefix[prefix]); - } +var call_id = 0; +var port; +var calls_waiting = new Map(); + +function set_call_callback(resolve, reject, func, args) +{ + port.postMessage([call_id, func, args]); + calls_waiting.set(call_id++, [resolve, reject]); +} + +async function remote_call(func, args) +{ + return new Promise((resolve, reject) => + set_call_callback(resolve, reject, func, args)); +} + +function handle_message(message) +{ + let callbacks = calls_waiting.get(message.call_id); + if (callbacks === undefined) { + handle_change(message); + return; + } + + let [resolve, reject] = callbacks; + calls_waiting.delete(message.call_id); + if (message.error !== undefined) + setTimeout(reject, 0, message.error); + else + setTimeout(resolve, 0, message.result); +} + +function list(name, prefix) +{ + return {prefix, name, listeners : new Set()}; +} + +var scripts = list("scripts", TYPE_PREFIX.SCRIPT); +var bags = list("bags", TYPE_PREFIX.BAG); +var pages = list("pages", TYPE_PREFIX.PAGE); + +const list_by_prefix = { + [TYPE_PREFIX.SCRIPT] : scripts, + [TYPE_PREFIX.BAG] : bags, + [TYPE_PREFIX.PAGE] : pages +}; + +var resolve_init; + +function handle_first_message(message) +{ + for (let prefix of Object.keys(message)) + list_by_prefix[prefix].map = new Map(message[prefix]); + + port.onMessage.removeListener(handle_first_message); + port.onMessage.addListener(handle_message); + + resolve_init(); +} + +function handle_change(change) +{ + let list = list_by_prefix[change.prefix]; + + if (change.new_val === undefined) + list.map.delete(change.item); + else + list.map.set(change.item, change.new_val); + + for (let listener_callback of list.listeners) + listener_callback(change); +} + +var exports = {}; + +function start_connection(resolve) +{ + resolve_init = resolve; + port = browser.runtime.connect({name : CONNECTION_TYPE.REMOTE_STORAGE}); + port.onMessage.addListener(handle_first_message); +} + +async function init() { + await new Promise((resolve, reject) => start_connection(resolve)); + return exports; +} + +for (let call_name of ["set", "remove", "replace", "clear"]) + exports [call_name] = (...args) => remote_call(call_name, args); + +// TODO: Much of the code below is copy-pasted from /background/storage.mjs. +// This should later be refactored into a separate module +// to avoid duplication. + +/* + * Facilitate listening to changes + */ - window.get_storage = make_once(init); -})(); +exports.add_change_listener = function (cb, prefixes=list_prefixes) +{ + if (typeof(prefixes) === "string") + prefixes = [prefixes]; + + for (let prefix of prefixes) + list_by_prefix[prefix].listeners.add(cb); +} + +exports.remove_change_listener = function (cb, prefixes=list_prefixes) +{ + if (typeof(prefixes) === "string") + prefixes = [prefixes]; + + for (let prefix of prefixes) + list_by_prefix[prefix].listeners.delete(cb); +} + +/* Prepare some hepler functions to get elements of a list */ + +function list_items_it(list, with_values=false) +{ + return with_values ? list.map.entries() : list.map.keys(); +} + +function list_entries_it(list) +{ + return list_items_it(list, true); +} + +function list_items(list, with_values=false) +{ + let array = []; + + for (let item of list_items_it(list, with_values)) + array.push(item); + + return array; +} + +function list_entries(list) +{ + return list_items(list, true); +} + +exports.get = function (prefix, item) +{ + return list_by_prefix[prefix].map.get(item); +} + +exports.get_all_names = function (prefix) +{ + return list_items(list_by_prefix[prefix]); +} + +exports.get_all_names_it = function (prefix) +{ + return list_items_it(list_by_prefix[prefix]); +} + +exports.get_all = function (prefix) +{ + return list_entries(list_by_prefix[prefix]); +} + +exports.get_all_it = function (prefix) +{ + return list_entries_it(list_by_prefix[prefix]); +} + +const get_remote_storage = make_once(init); + +/* + * EXPORTS_START + * EXPORT get_remote_storage + * EXPORTS_END + */ diff --git a/common/stored_types.js b/common/stored_types.js index e043777..ef1339f 100644 --- a/common/stored_types.js +++ b/common/stored_types.js @@ -13,29 +13,29 @@ * an underscore. */ -"use strict"; +const TYPE_PREFIX = { + PAGE : "p", + BAG : "b", + SCRIPT : "s", + VAR : "_" +}; -(() => { - const TYPE_PREFIX = { - PAGE : "p", - BAG : "b", - SCRIPT : "s", - VAR : "_" - }; +const TYPE_NAME = { + [TYPE_PREFIX.PAGE] : "page", + [TYPE_PREFIX.BAG] : "bag", + [TYPE_PREFIX.SCRIPT] : "script" +} - const TYPE_NAME = { - [TYPE_PREFIX.PAGE] : "page", - [TYPE_PREFIX.BAG] : "bag", - [TYPE_PREFIX.SCRIPT] : "script" - } +const list_prefixes = [ + TYPE_PREFIX.PAGE, + TYPE_PREFIX.BAG, + TYPE_PREFIX.SCRIPT +]; - const list_prefixes = [ - TYPE_PREFIX.PAGE, - TYPE_PREFIX.BAG, - TYPE_PREFIX.SCRIPT - ]; - - window.TYPE_PREFIX = TYPE_PREFIX; - window.TYPE_NAME = TYPE_NAME; - window.list_prefixes = list_prefixes; -})(); +/* + * EXPORTS_START + * EXPORT TYPE_PREFIX + * EXPORT TYPE_NAME + * EXPORT list_prefixes + * EXPORTS_END + */ diff --git a/common/url_item.js b/common/url_item.js index 9f4b118..102f117 100644 --- a/common/url_item.js +++ b/common/url_item.js @@ -5,15 +5,15 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; +function url_item(url) +{ + let url_re = /^([^?#]*).*$/; + let match = url_re.exec(url); + return match[1]; +} -(() => { - function url_item(url) - { - let url_re = /^([^?#]*).*$/; - let match = url_re.exec(url); - return match[1]; - } - - window.url_item = url_item; -})(); +/* + * EXPORTS_START + * EXPORT url_item + * EXPORTS_END + */ diff --git a/content/freezer.js b/content/freezer.js index cdd0709..1696f53 100644 --- a/content/freezer.js +++ b/content/freezer.js @@ -6,58 +6,60 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; +const loaderAttributes = ["href", "src", "data"]; +const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i; -(() => { - const loaderAttributes = ["href", "src", "data"]; - const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i; +function sanitize_attributes(element) { + if (element._frozen) + return; + let fa = []; + let loaders = []; + let attributes = element.attributes || []; - function sanitizeAttributes(element) { - if (element._frozen) - return; - let fa = []; - let loaders = []; - for (let a of element.attributes) { - let name = a.localName.toLowerCase(); - if (loaderAttributes.includes(name)) - if (jsOrDataUrlRx.test(a.value)) - loaders.push(a); + for (let a of attributes) { + let name = a.localName.toLowerCase(); + if (loaderAttributes.includes(name)) + if (jsOrDataUrlRx.test(a.value)) + loaders.push(a); - else if (name.startsWith("on")) { - console.debug("Removing", a, element.outerHTML); - fa.push(a.cloneNode()); - a.value = ""; - element[name] = null; - } + else if (name.startsWith("on")) { + console.debug("Removing", a, element.outerHTML); + fa.push(a.cloneNode()); + a.value = ""; + element[name] = null; } - if (loaders.length) { - for (let a of loaders) { - fa.push(a.cloneNode()); - a.value = "javascript://frozen"; - } - if ("contentWindow" in element) - element.replaceWith(element = element.cloneNode(true)); - + } + if (loaders.length) { + for (let a of loaders) { + fa.push(a.cloneNode()); + a.value = "javascript://frozen"; } - if (fa.length) - element._frozenAttributes = fa; - element._frozen = true; + if ("contentWindow" in element) + element.replaceWith(element = element.cloneNode(true)); + } - - function scriptSuppressor(nonce) { - const blockExecute = e => { - if (document.readyState === 'complete') { - removeEventListener('beforescriptexecute', blockExecute, true); - return; - } - else if (e.isTrusted && e.target.getAttribute('nonce') !== nonce) { // Prevent blocking of injected scripts - e.preventDefault(); - console.log('Suppressed script', e.target); - } - }; - return blockExecute; + if (fa.length) + element._frozenAttributes = fa; + element._frozen = true; +} + +function script_suppressor(nonce) { + const blockExecute = e => { + if (document.readyState === 'complete') { + removeEventListener('beforescriptexecute', blockExecute, true); + return; + } + else if (e.isTrusted && e.target.getAttribute('nonce') !== nonce) { // Prevent blocking of injected scripts + e.preventDefault(); + console.log('Suppressed script', e.target); + } }; - - window.scriptSuppressor = scriptSuppressor; - window.sanitize_attributes = sanitizeAttributes; -})(); + return blockExecute; +}; + +/* + * EXPORTS_START + * EXPORT script_suppressor + * EXPORT sanitize_attributes + * EXPORTS_END + */ diff --git a/content/main.js b/content/main.js index 2a46c7e..d55ee2e 100644 --- a/content/main.js +++ b/content/main.js @@ -5,113 +5,133 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; +/* + * IMPORTS_START + * IMPORT handle_page_actions + * IMPORT url_item + * IMPORT gen_unique + * IMPORT sanitize_attributes + * IMPORT script_suppressor + * IMPORT is_chrome + * IMPORT is_mozilla + * IMPORTS_END + */ -(() => { - const handle_page_actions = window.handle_page_actions; - const url_item = window.url_item; - const gen_unique = window.gen_unique; - const sanitize_attributes = window.sanitize_attributes; +/* + * Due to some technical limitations the chosen method of whitelisting sites + * is to smuggle whitelist indicator in page's url as a "magical" string + * after '#'. Right now this is not needed in HTTP(s) pages where native + * script blocking happens through CSP header injection but is needed for + * protocols like ftp:// and file://. + * + * The code that actually injects the magical string into ftp:// and file:// + * urls has not yet been added to the extension. + */ - /* - * Due to some technical limitations the chosen method of whitelisting sites - * is to smuggle whitelist indicator in page's url as a "magical" string - * after '#'. Right now this is not needed in HTTP(s) pages where native - * script blocking happens through CSP header injection but is needed for - * protocols like ftp:// and file://. - * - * The code that actually injects the magical string into ftp:// and file:// - * urls has not yet been added to the extension. - */ +let url = url_item(document.URL); +let unique = gen_unique(url); +let nonce = unique.substring(1); - let url = url_item(document.URL); - let unique = gen_unique(url); - let nonce = unique.substring(1); - - const scriptSuppressor = window.scriptSuppressor(nonce); - - function needs_blocking() - { - if (url.startsWith("https://") || url.startsWith("http://")) - return false; - - let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; - let match = url_re.exec(document.URL); - let base_url = match[1]; - let first_target = match[3]; - let second_target = match[4]; - - if (first_target !== undefined && - first_target === unique) { - if (second_target !== undefined) - window.location.href = base_url + second_target; - else - history.replaceState(null, "", base_url); - - console.log(["allowing whitelisted", document.URL]); - return false; - } - - console.log(["disallowing", document.URL]); - return true; - } +const suppressor = script_suppressor(nonce); + +function needs_blocking() +{ + if (url.startsWith("https://") || url.startsWith("http://")) + return false; - function handle_mutation(mutations, observer) - { - if (document.readyState === 'complete') { - console.log("complete"); - observer.disconnect(); - return; - } - for (let mutation of mutations) { - for (let node of mutation.addedNodes) { - /* - * Modifying <script> element doesn't always prevent its - * execution in some Mozilla browsers. Additional blocking - * through CSP meta tag injection is required. - */ - if (node.tagName === "SCRIPT") { - block_script(node); - continue; - } - - sanitize_attributes(node); - - if (node.tagName === "HEAD") - inject_csp(node); - } - } + let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; + let match = url_re.exec(document.URL); + let base_url = match[1]; + let first_target = match[3]; + let second_target = match[4]; + + if (first_target !== undefined && + first_target === unique) { + if (second_target !== undefined) + window.location.href = base_url + second_target; + else + history.replaceState(null, "", base_url); + + console.log(["allowing whitelisted", document.URL]); + return false; } - function block_script(node) - { - console.log(node); - - /* - * Disabling scripts this way allows them to still be relatively - * easily accessed in case they contain some useful data. - */ - if (node.hasAttribute("type")) - node.setAttribute("blocked-type", node.getAttribute("type")); - node.setAttribute("type", "application/json"); + console.log(["disallowing", document.URL]); + return true; +} + +function handle_mutation(mutations, observer) +{ + if (document.readyState === 'complete') { + console.log("mutation handling complete"); + observer.disconnect(); + return; } + for (const mutation of mutations) { + for (const node of mutation.addedNodes) + block_node(node); + } +} + +function block_nodes_recursively(node) +{ + block_node(node); + for (const child of node.children) + block_nodes_recursively(child); +} - function inject_csp(node) - { - console.log('injecting CSP'); - let meta = document.createElement("meta"); - meta.setAttribute("http-equiv", "Content-Security-Policy"); - meta.setAttribute("content", `\ -script-src 'nonce-${nonce}'; \ -script-src-elem 'nonce-${nonce}';\ -`); - node.appendChild(meta); +function block_node(node) +{ + /* + * Modifying <script> element doesn't always prevent its + * execution in some Mozilla browsers. Additional blocking + * through CSP meta tag injection is required. + */ + if (node.tagName === "SCRIPT") { + block_script(node); + return; } - if (needs_blocking()) { - // Script blocking for Gecko - addEventListener('beforescriptexecute', scriptSuppressor, true); - + sanitize_attributes(node); + + if (node.tagName === "HEAD") + inject_csp(node); +} + +function block_script(node) +{ + /* + * Disabling scripts this way allows them to still be relatively + * easily accessed in case they contain some useful data. + */ + if (node.hasAttribute("type")) + node.setAttribute("blocked-type", node.getAttribute("type")); + node.setAttribute("type", "application/json"); +} + +function inject_csp(head) +{ + console.log('injecting CSP'); + + let meta = document.createElement("meta"); + meta.setAttribute("http-equiv", "Content-Security-Policy"); + + let rule = `script-src 'nonce-${nonce}'; `; + if (is_chrome) + rule += `script-src-elem 'nonce-${nonce}';`; + + meta.setAttribute("content", rule); + + if (head.firstElementChild === null) + head.appendChild(meta); + else + head.insertBefore(meta, head.firstElementChild); +} + +if (needs_blocking()) { + block_nodes_recursively(document.documentElement); + + if (is_chrome) { var observer = new MutationObserver(handle_mutation); observer.observe(document.documentElement, { attributes: true, @@ -120,5 +140,8 @@ script-src-elem 'nonce-${nonce}';\ }); } - handle_page_actions(nonce); -})(); + if (is_mozilla) + addEventListener('beforescriptexecute', suppressor, true); +} + +handle_page_actions(nonce); diff --git a/content/page_actions.js b/content/page_actions.js index 88332a8..bc65449 100644 --- a/content/page_actions.js +++ b/content/page_actions.js @@ -5,60 +5,60 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const CONNECTION_TYPE = window.CONNECTION_TYPE; - const browser = window.browser; - - var port; - var loaded = false; - var scripts_awaiting = []; - var nonce; +/* + * IMPORTS_START + * IMPORT CONNECTION_TYPE + * IMPORT browser + * IMPORTS_END + */ - function handle_message(message) - { - console.log(["message", message]); +var port; +var loaded = false; +var scripts_awaiting = []; +var nonce; - if (message.inject === undefined) - return; +function handle_message(message) +{ + if (message.inject === undefined) + return; - for (let script_text of message.inject) { - if (loaded) - add_script(script_text); - else - scripts_awaiting.push(script_text); - } + for (let script_text of message.inject) { + if (loaded) + add_script(script_text); + else + scripts_awaiting.push(script_text); } +} - function document_loaded(event) - { - console.log("loaded"); - - loaded = true; +function document_loaded(event) +{ + loaded = true; - for (let script_text of scripts_awaiting) - add_script(script_text); + for (let script_text of scripts_awaiting) + add_script(script_text); - scripts_awaiting = undefined; - } + scripts_awaiting = undefined; +} - function add_script(script_text) - { - let script = document.createElement("script"); - script.textContent = script_text; - script.setAttribute("nonce", nonce); - document.body.appendChild(script); - } +function add_script(script_text) +{ + let script = document.createElement("script"); + script.textContent = script_text; + script.setAttribute("nonce", nonce); + document.body.appendChild(script); +} - function handle_page_actions(script_nonce) { - document.addEventListener("DOMContentLoaded", document_loaded); - port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS}); - port.onMessage.addListener(handle_message); - port.postMessage({url: document.URL}); +function handle_page_actions(script_nonce) { + document.addEventListener("DOMContentLoaded", document_loaded); + port = browser.runtime.connect({name : CONNECTION_TYPE.PAGE_ACTIONS}); + port.onMessage.addListener(handle_message); + port.postMessage({url: document.URL}); - nonce = script_nonce; - } + nonce = script_nonce; +} - window.handle_page_actions = handle_page_actions; -})(); +/* + * EXPORTS_START + * EXPORT handle_page_actions + * EXPORTS_END + */ @@ -6,6 +6,10 @@ Files: * Copyright: 2021 Wojtek Kosior <koszko@koszko.org> License: GPL-3+-javascript or Alicense-1.0 +Files: build.sh +Copyright: 2021 Wojtek Kosior <koszko@koszko.org> +License: CC0 + Files: manifest.json Copyright: 2021 Wojtek Kosior <koszko@koszko.org> 2021 jahoti <jahoti@tilde.team> diff --git a/html/display-panel.html b/html/display-panel.html index 40bb535..5e5580e 100644 --- a/html/display-panel.html +++ b/html/display-panel.html @@ -9,8 +9,6 @@ <title>Myext popup</title> </head> <body> - <button id="settings_but" type="button">Settings</button> - <script src="/common/browser.js"></script> - <script src="./display-panel.js"></script> + <button id="settings_but" type="button">Settings</button>_POPUPSCRIPTS_ </body> </html> diff --git a/html/display-panel.js b/html/display-panel.js index 9bd683c..4a4cdcd 100644 --- a/html/display-panel.js +++ b/html/display-panel.js @@ -5,11 +5,11 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const browser = window.browser; +/* + * IMPORTS_START + * IMPORT browser + * IMPORTS_END + */ - document.getElementById("settings_but") - .addEventListener("click", (e) => browser.runtime.openOptionsPage()); -})(); +document.getElementById("settings_but") + .addEventListener("click", (e) => browser.runtime.openOptionsPage()); diff --git a/html/options.html b/html/options.html index c380fc4..03978c7 100644 --- a/html/options.html +++ b/html/options.html @@ -249,13 +249,6 @@ <a id="file_downloader" class="hide"></a> <form id="file_opener_form" style="visibility: hidden;"> <input type="file" id="file_opener"></input> - </form> - - <script src="/common/connection_types.js"></script> - <script src="/common/stored_types.js"></script> - <script src="/common/once.js"></script> - <script src="/common/browser.js"></script> - <script src="/common/storage_client.js"></script> - <script src="./options_main.js"></script> + </form>_OPTIONSSCRIPTS_ </body> </html> diff --git a/html/options_main.js b/html/options_main.js index ef01e60..6f203fa 100644 --- a/html/options_main.js +++ b/html/options_main.js @@ -5,746 +5,746 @@ * Redistribution terms are gathered in the `copyright' file. */ -"use strict"; - -(() => { - const get_storage = window.get_storage; - const TYPE_PREFIX = window.TYPE_PREFIX; - const TYPE_NAME = window.TYPE_NAME; - const list_prefixes = window.list_prefixes; - - var storage; - function by_id(id) - { - return document.getElementById(id); - } - - function nice_name(prefix, name) - { - return `${name} (${TYPE_NAME[prefix]})`; - } - - const item_li_template = by_id("item_li_template"); - const bag_component_li_template = by_id("bag_component_li_template"); - const chbx_component_li_template = by_id("chbx_component_li_template"); - const radio_component_li_template = by_id("radio_component_li_template"); - const import_li_template = by_id("import_li_template"); - /* Make sure they are later cloned without id. */ - item_li_template.removeAttribute("id"); - bag_component_li_template.removeAttribute("id"); - chbx_component_li_template.removeAttribute("id"); - radio_component_li_template.removeAttribute("id"); - import_li_template.removeAttribute("id"); - - function item_li_id(prefix, item) - { - return `li_${prefix}_${item}`; - } - - /* Insert into list of bags/pages/scripts */ - function add_li(prefix, item, at_the_end=false) - { - let ul = ul_by_prefix[prefix]; - let li = item_li_template.cloneNode(true); - li.id = item_li_id(prefix, item); - - let span = li.firstElementChild; - span.textContent = item; - - let edit_button = span.nextElementSibling; - edit_button.addEventListener("click", () => edit_item(prefix, item)); - - let remove_button = edit_button.nextElementSibling; - remove_button.addEventListener("click", - () => storage.remove(prefix, item)); - - let export_button = remove_button.nextElementSibling; - export_button.addEventListener("click", - () => export_item(prefix, item)); +/* + * IMPORTS_START + * IMPORT get_remote_storage + * IMPORT TYPE_PREFIX + * IMPORT TYPE_NAME + * IMPORT list_prefixes + * IMPORTS_END + */ - if (!at_the_end) { - for (let element of ul.ul.children) { - if (element.id < li.id || element.id.startsWith("work_")) - continue; +var storage; +function by_id(id) +{ + return document.getElementById(id); +} + +function nice_name(prefix, name) +{ + return `${name} (${TYPE_NAME[prefix]})`; +} + +const item_li_template = by_id("item_li_template"); +const bag_component_li_template = by_id("bag_component_li_template"); +const chbx_component_li_template = by_id("chbx_component_li_template"); +const radio_component_li_template = by_id("radio_component_li_template"); +const import_li_template = by_id("import_li_template"); +/* Make sure they are later cloned without id. */ +item_li_template.removeAttribute("id"); +bag_component_li_template.removeAttribute("id"); +chbx_component_li_template.removeAttribute("id"); +radio_component_li_template.removeAttribute("id"); +import_li_template.removeAttribute("id"); + +function item_li_id(prefix, item) +{ + return `li_${prefix}_${item}`; +} + +/* Insert into list of bags/pages/scripts */ +function add_li(prefix, item, at_the_end=false) +{ + let ul = ul_by_prefix[prefix]; + let li = item_li_template.cloneNode(true); + li.id = item_li_id(prefix, item); + + let span = li.firstElementChild; + span.textContent = item; + + let edit_button = span.nextElementSibling; + edit_button.addEventListener("click", () => edit_item(prefix, item)); + + let remove_button = edit_button.nextElementSibling; + remove_button.addEventListener("click", + () => storage.remove(prefix, item)); + + let export_button = remove_button.nextElementSibling; + export_button.addEventListener("click", + () => export_item(prefix, item)); + + if (!at_the_end) { + for (let element of ul.ul.children) { + if (element.id < li.id || element.id.startsWith("work_")) + continue; - ul.ul.insertBefore(li, element); - return; - } + ul.ul.insertBefore(li, element); + return; } - - ul.ul.appendChild(li); } - const chbx_components_ul = by_id("chbx_components_ul"); - const radio_components_ul = by_id("radio_components_ul"); + ul.ul.appendChild(li); +} - function chbx_li_id(prefix, item) - { - return `cli_${prefix}_${item}`; - } +const chbx_components_ul = by_id("chbx_components_ul"); +const radio_components_ul = by_id("radio_components_ul"); - function radio_li_id(prefix, item) - { - return `rli_${prefix}_${item}`; - } +function chbx_li_id(prefix, item) +{ + return `cli_${prefix}_${item}`; +} - //TODO: refactor the 2 functions below +function radio_li_id(prefix, item) +{ + return `rli_${prefix}_${item}`; +} - function add_chbx_li(prefix, name) - { - if (prefix === TYPE_PREFIX.PAGE) - return; +//TODO: refactor the 2 functions below - let li = chbx_component_li_template.cloneNode(true); - li.id = chbx_li_id(prefix, name); - li.setAttribute("data-prefix", prefix); - li.setAttribute("data-name", name); - - let chbx = li.firstElementChild; - let span = chbx.nextElementSibling; - - span.textContent = nice_name(prefix, name); - - chbx_components_ul.appendChild(li); - } - - var radio_component_none_li = by_id("radio_component_none_li"); +function add_chbx_li(prefix, name) +{ + if (prefix === TYPE_PREFIX.PAGE) + return; - function add_radio_li(prefix, name) - { - if (prefix === TYPE_PREFIX.PAGE) - return; + let li = chbx_component_li_template.cloneNode(true); + li.id = chbx_li_id(prefix, name); + li.setAttribute("data-prefix", prefix); + li.setAttribute("data-name", name); - let li = radio_component_li_template.cloneNode(true); - li.id = radio_li_id(prefix, name); - li.setAttribute("data-prefix", prefix); - li.setAttribute("data-name", name); + let chbx = li.firstElementChild; + let span = chbx.nextElementSibling; - let radio = li.firstElementChild; - let span = radio.nextElementSibling; + span.textContent = nice_name(prefix, name); - span.textContent = nice_name(prefix, name); + chbx_components_ul.appendChild(li); +} - radio_components_ul.insertBefore(li, radio_component_none_li); - } - - const page_payload_span = by_id("page_payload"); - - function set_page_components(components) - { - if (components === undefined) { - page_payload_span.setAttribute("data-payload", "no"); - page_payload_span.textContent = "(None)"; - } else { - page_payload_span.setAttribute("data-payload", "yes"); - let [prefix, name] = components; - page_payload_span.setAttribute("data-prefix", prefix); - page_payload_span.setAttribute("data-name", name); - page_payload_span.textContent = nice_name(prefix, name); - } - } +var radio_component_none_li = by_id("radio_component_none_li"); - const page_allow_chbx = by_id("page_allow_chbx"); +function add_radio_li(prefix, name) +{ + if (prefix === TYPE_PREFIX.PAGE) + return; - /* Used to reset edited page. */ - function reset_work_page_li(ul, item, settings) - { - ul.work_name_input.value = maybe_string(item); - settings = settings || {allow: false, components: undefined}; - page_allow_chbx.checked = !!settings.allow; + let li = radio_component_li_template.cloneNode(true); + li.id = radio_li_id(prefix, name); + li.setAttribute("data-prefix", prefix); + li.setAttribute("data-name", name); - set_page_components(settings.components); - } + let radio = li.firstElementChild; + let span = radio.nextElementSibling; - function work_page_li_components() - { - if (page_payload_span.getAttribute("data-payload") === "no") - return undefined; + span.textContent = nice_name(prefix, name); - let prefix = page_payload_span.getAttribute("data-prefix"); - let name = page_payload_span.getAttribute("data-name"); - return [prefix, name]; - } + radio_components_ul.insertBefore(li, radio_component_none_li); +} - /* Used to get edited page data for saving. */ - function work_page_li_data(ul) - { - let url = ul.work_name_input.value; - let settings = { - components : work_page_li_components(), - allow : !!page_allow_chbx.checked - }; +const page_payload_span = by_id("page_payload"); - return [url, settings]; +function set_page_components(components) +{ + if (components === undefined) { + page_payload_span.setAttribute("data-payload", "no"); + page_payload_span.textContent = "(None)"; + } else { + page_payload_span.setAttribute("data-payload", "yes"); + let [prefix, name] = components; + page_payload_span.setAttribute("data-prefix", prefix); + page_payload_span.setAttribute("data-name", name); + page_payload_span.textContent = nice_name(prefix, name); } +} - const empty_bag_component_li = by_id("empty_bag_component_li"); - var bag_components_ul = by_id("bag_components_ul"); - - /* Used to construct and update components list of edited bag. */ - function add_bag_components(components) - { - for (let component of components) { - let [prefix, name] = component; - let li = bag_component_li_template.cloneNode(true); - li.setAttribute("data-prefix", prefix); - li.setAttribute("data-name", name); - let span = li.firstElementChild; - span.textContent = nice_name(prefix, name); - let remove_but = span.nextElementSibling; - remove_but.addEventListener("click", () => - bag_components_ul.removeChild(li)); - bag_components_ul.appendChild(li); - } - - bag_components_ul.appendChild(empty_bag_component_li); - } +const page_allow_chbx = by_id("page_allow_chbx"); - /* Used to reset edited bag. */ - function reset_work_bag_li(ul, item, components) - { - components = components || []; +/* Used to reset edited page. */ +function reset_work_page_li(ul, item, settings) +{ + ul.work_name_input.value = maybe_string(item); + settings = settings || {allow: false, components: undefined}; + page_allow_chbx.checked = !!settings.allow; - ul.work_name_input.value = maybe_string(item); - let old_components_ul = bag_components_ul; - bag_components_ul = old_components_ul.cloneNode(false); + set_page_components(settings.components); +} - ul.work_li.insertBefore(bag_components_ul, old_components_ul); - ul.work_li.removeChild(old_components_ul); +function work_page_li_components() +{ + if (page_payload_span.getAttribute("data-payload") === "no") + return undefined; - add_bag_components(components); - } + let prefix = page_payload_span.getAttribute("data-prefix"); + let name = page_payload_span.getAttribute("data-name"); + return [prefix, name]; +} - /* Used to get edited bag data for saving. */ - function work_bag_li_data(ul) - { - let components_ul = ul.work_name_input.nextElementSibling; - let component_li = components_ul.firstElementChild; +/* Used to get edited page data for saving. */ +function work_page_li_data(ul) +{ + let url = ul.work_name_input.value; + let settings = { + components : work_page_li_components(), + allow : !!page_allow_chbx.checked + }; - let components = []; + return [url, settings]; +} - /* Last list element is empty li with id set. */ - while (component_li.id === '') { - components.push([component_li.getAttribute("data-prefix"), - component_li.getAttribute("data-name")]); - component_li = component_li.nextElementSibling; - } +const empty_bag_component_li = by_id("empty_bag_component_li"); +var bag_components_ul = by_id("bag_components_ul"); - return [ul.work_name_input.value, components]; +/* Used to construct and update components list of edited bag. */ +function add_bag_components(components) +{ + for (let component of components) { + let [prefix, name] = component; + let li = bag_component_li_template.cloneNode(true); + li.setAttribute("data-prefix", prefix); + li.setAttribute("data-name", name); + let span = li.firstElementChild; + span.textContent = nice_name(prefix, name); + let remove_but = span.nextElementSibling; + remove_but.addEventListener("click", () => + bag_components_ul.removeChild(li)); + bag_components_ul.appendChild(li); } - const script_url_input = by_id("script_url_field"); - const script_sha256_input = by_id("script_sha256_field"); - const script_contents_field = by_id("script_contents_field"); + bag_components_ul.appendChild(empty_bag_component_li); +} - function maybe_string(maybe_defined) - { - return maybe_defined === undefined ? "" : maybe_defined + ""; - } +/* Used to reset edited bag. */ +function reset_work_bag_li(ul, item, components) +{ + components = components || []; - /* Used to reset edited script. */ - function reset_work_script_li(ul, name, data) - { - ul.work_name_input.value = maybe_string(name); - if (data === undefined) - data = {}; - script_url_input.value = maybe_string(data.url); - script_sha256_input.value = maybe_string(data.hash); - script_contents_field.value = maybe_string(data.text); - } + ul.work_name_input.value = maybe_string(item); + let old_components_ul = bag_components_ul; + bag_components_ul = old_components_ul.cloneNode(false); - /* Used to get edited script data for saving. */ - function work_script_li_data(ul) - { - return [ul.work_name_input.value, { - url : script_url_input.value, - hash : script_sha256_input.value, - text : script_contents_field.value - }]; - } + ul.work_li.insertBefore(bag_components_ul, old_components_ul); + ul.work_li.removeChild(old_components_ul); - function cancel_work(prefix) - { - let ul = ul_by_prefix[prefix]; + add_bag_components(components); +} - if (ul.state === UL_STATE.IDLE) - return; +/* Used to get edited bag data for saving. */ +function work_bag_li_data(ul) +{ + let components_ul = ul.work_name_input.nextElementSibling; + let component_li = components_ul.firstElementChild; - if (ul.state === UL_STATE.EDITING_ENTRY) { - add_li(prefix, ul.edited_item); - } + let components = []; - ul.work_li.classList.add("hide"); - ul.state = UL_STATE.IDLE; + /* Last list element is empty li with id set. */ + while (component_li.id === '') { + components.push([component_li.getAttribute("data-prefix"), + component_li.getAttribute("data-name")]); + component_li = component_li.nextElementSibling; } - function save_work(prefix) - { - let ul = ul_by_prefix[prefix]; + return [ul.work_name_input.value, components]; +} - if (ul.state === UL_STATE.IDLE) - return; +const script_url_input = by_id("script_url_field"); +const script_sha256_input = by_id("script_sha256_field"); +const script_contents_field = by_id("script_contents_field"); - let [item, data] = ul.get_work_li_data(ul); +function maybe_string(maybe_defined) +{ + return maybe_defined === undefined ? "" : maybe_defined + ""; +} - /* Here we fire promises and return without waiting. */ +/* Used to reset edited script. */ +function reset_work_script_li(ul, name, data) +{ + ul.work_name_input.value = maybe_string(name); + if (data === undefined) + data = {}; + script_url_input.value = maybe_string(data.url); + script_sha256_input.value = maybe_string(data.hash); + script_contents_field.value = maybe_string(data.text); +} - if (ul.state === UL_STATE.EDITING_ENTRY) - storage.replace(prefix, ul.edited_item, item, data); - if (ul.state === UL_STATE.ADDING_ENTRY) - storage.set(prefix, item, data); +/* Used to get edited script data for saving. */ +function work_script_li_data(ul) +{ + return [ul.work_name_input.value, { + url : script_url_input.value, + hash : script_sha256_input.value, + text : script_contents_field.value + }]; +} - cancel_work(prefix); - } +function cancel_work(prefix) +{ + let ul = ul_by_prefix[prefix]; - function edit_item(prefix, item) - { - cancel_work(prefix); + if (ul.state === UL_STATE.IDLE) + return; - let ul = ul_by_prefix[prefix]; - let li = by_id(item_li_id(prefix, item)); - ul.reset_work_li(ul, item, storage.get(prefix, item)); - ul.ul.insertBefore(ul.work_li, li); - ul.ul.removeChild(li); - ul.work_li.classList.remove("hide"); - - ul.state = UL_STATE.EDITING_ENTRY; - ul.edited_item = item; + if (ul.state === UL_STATE.EDITING_ENTRY) { + add_li(prefix, ul.edited_item); } - const file_downloader = by_id("file_downloader"); + ul.work_li.classList.add("hide"); + ul.state = UL_STATE.IDLE; +} - function recursively_export_item(prefix, name, added_items, items_data) - { - let key = prefix + name; +function save_work(prefix) +{ + let ul = ul_by_prefix[prefix]; - if (added_items.has(key)) - return; + if (ul.state === UL_STATE.IDLE) + return; - let data = storage.get(prefix, name); - if (data === undefined) { - console.log(`${TYPE_NAME[prefix]} '${name}' for export not found`); - return; - } + let [item, data] = ul.get_work_li_data(ul); - if (prefix !== TYPE_PREFIX.SCRIPT) { - let components = prefix === TYPE_PREFIX.BAG ? - data : [data.components]; + /* Here we fire promises and return without waiting. */ - for (let [comp_prefix, comp_name] of components) { - recursively_export_item(comp_prefix, comp_name, - added_items, items_data); - } - } + if (ul.state === UL_STATE.EDITING_ENTRY) + storage.replace(prefix, ul.edited_item, item, data); + if (ul.state === UL_STATE.ADDING_ENTRY) + storage.set(prefix, item, data); - items_data.push({[key]: data}); - added_items.add(key); - } + cancel_work(prefix); +} - function export_item(prefix, name) - { - let added_items = new Set(); - let items_data = []; - recursively_export_item(prefix, name, added_items, items_data); - let file = new Blob([JSON.stringify(items_data)], - {type: "application/json"}); - let url = URL.createObjectURL(file); - file_downloader.setAttribute("href", url); - file_downloader.setAttribute("download", prefix + name + ".json"); - file_downloader.click(); - file_downloader.removeAttribute("href"); - URL.revokeObjectURL(url); - } +function edit_item(prefix, item) +{ + cancel_work(prefix); - function add_new_item(prefix) - { - cancel_work(prefix); + let ul = ul_by_prefix[prefix]; + let li = by_id(item_li_id(prefix, item)); + ul.reset_work_li(ul, item, storage.get(prefix, item)); + ul.ul.insertBefore(ul.work_li, li); + ul.ul.removeChild(li); + ul.work_li.classList.remove("hide"); - let ul = ul_by_prefix[prefix]; - ul.reset_work_li(ul); - ul.work_li.classList.remove("hide"); - ul.ul.appendChild(ul.work_li); + ul.state = UL_STATE.EDITING_ENTRY; + ul.edited_item = item; +} - ul.state = UL_STATE.ADDING_ENTRY; - } +const file_downloader = by_id("file_downloader"); - const chbx_components_window = by_id("chbx_components_window"); +function recursively_export_item(prefix, name, added_items, items_data) +{ + let key = prefix + name; - function bag_components() - { - chbx_components_window.classList.remove("hide"); - radio_components_window.classList.add("hide"); + if (added_items.has(key)) + return; - for (let li of chbx_components_ul.children) { - let chbx = li.firstElementChild; - chbx.checked = false; - } + let data = storage.get(prefix, name); + if (data === undefined) { + console.log(`${TYPE_NAME[prefix]} '${name}' for export not found`); + return; } - function commit_bag_components() - { - let selected = []; - - for (let li of chbx_components_ul.children) { - let chbx = li.firstElementChild; - if (!chbx.checked) - continue; - - selected.push([li.getAttribute("data-prefix"), - li.getAttribute("data-name")]); + if (prefix !== TYPE_PREFIX.SCRIPT) { + let components = prefix === TYPE_PREFIX.BAG ? + data : [data.components]; + + for (let [comp_prefix, comp_name] of components) { + recursively_export_item(comp_prefix, comp_name, + added_items, items_data); } - - add_bag_components(selected); - cancel_components(); } - const radio_components_window = by_id("radio_components_window"); - var radio_component_none_input = by_id("radio_component_none_input"); - - function page_components() - { - radio_components_window.classList.remove("hide"); - chbx_components_window.classList.add("hide"); + items_data.push({[key]: data}); + added_items.add(key); +} + +function export_item(prefix, name) +{ + let added_items = new Set(); + let items_data = []; + recursively_export_item(prefix, name, added_items, items_data); + let file = new Blob([JSON.stringify(items_data)], + {type: "application/json"}); + let url = URL.createObjectURL(file); + file_downloader.setAttribute("href", url); + file_downloader.setAttribute("download", prefix + name + ".json"); + file_downloader.click(); + file_downloader.removeAttribute("href"); + URL.revokeObjectURL(url); +} + +function add_new_item(prefix) +{ + cancel_work(prefix); + + let ul = ul_by_prefix[prefix]; + ul.reset_work_li(ul); + ul.work_li.classList.remove("hide"); + ul.ul.appendChild(ul.work_li); + + ul.state = UL_STATE.ADDING_ENTRY; +} + +const chbx_components_window = by_id("chbx_components_window"); + +function bag_components() +{ + chbx_components_window.classList.remove("hide"); + radio_components_window.classList.add("hide"); + + for (let li of chbx_components_ul.children) { + let chbx = li.firstElementChild; + chbx.checked = false; + } +} - radio_component_none_input.checked = true; +function commit_bag_components() +{ + let selected = []; - let components = work_page_li_components(); - if (components === undefined) - return; + for (let li of chbx_components_ul.children) { + let chbx = li.firstElementChild; + if (!chbx.checked) + continue; - let [prefix, item] = components; - let li = by_id(radio_li_id(prefix, item)); - if (li === null) - radio_component_none_input.checked = false; - else - li.firstElementChild.checked = true; + selected.push([li.getAttribute("data-prefix"), + li.getAttribute("data-name")]); } - function commit_page_components() - { - let components = null; + add_bag_components(selected); + cancel_components(); +} - for (let li of radio_components_ul.children) { - let radio = li.firstElementChild; - if (!radio.checked) - continue; +const radio_components_window = by_id("radio_components_window"); +var radio_component_none_input = by_id("radio_component_none_input"); - components = [li.getAttribute("data-prefix"), - li.getAttribute("data-name")]; +function page_components() +{ + radio_components_window.classList.remove("hide"); + chbx_components_window.classList.add("hide"); - if (radio.id === "radio_component_none_input") - components = undefined; + radio_component_none_input.checked = true; - break; - } + let components = work_page_li_components(); + if (components === undefined) + return; - if (components !== null) - set_page_components(components); - cancel_components(); - } + let [prefix, item] = components; + let li = by_id(radio_li_id(prefix, item)); + if (li === null) + radio_component_none_input.checked = false; + else + li.firstElementChild.checked = true; +} - function cancel_components() - { - chbx_components_window.classList.add("hide"); - radio_components_window.classList.add("hide"); - } +function commit_page_components() +{ + let components = null; - const UL_STATE = { - EDITING_ENTRY : 0, - ADDING_ENTRY : 1, - IDLE : 2 - }; - - const ul_by_prefix = { - [TYPE_PREFIX.PAGE] : { - ul : by_id("pages_ul"), - work_li : by_id("work_page_li"), - work_name_input : by_id("page_url_field"), - reset_work_li : reset_work_page_li, - get_work_li_data : work_page_li_data, - select_components : page_components, - commit_components : commit_page_components, - state : UL_STATE.IDLE, - edited_item : undefined, - }, - [TYPE_PREFIX.BAG] : { - ul : by_id("bags_ul"), - work_li : by_id("work_bag_li"), - work_name_input : by_id("bag_name_field"), - reset_work_li : reset_work_bag_li, - get_work_li_data : work_bag_li_data, - select_components : bag_components, - commit_components : commit_bag_components, - state : UL_STATE.IDLE, - edited_item : undefined, - }, - [TYPE_PREFIX.SCRIPT] : { - ul : by_id("scripts_ul"), - work_li : by_id("work_script_li"), - work_name_input : by_id("script_name_field"), - reset_work_li : reset_work_script_li, - get_work_li_data : work_script_li_data, - state : UL_STATE.IDLE, - edited_item : undefined, - } - } + for (let li of radio_components_ul.children) { + let radio = li.firstElementChild; + if (!radio.checked) + continue; + + components = [li.getAttribute("data-prefix"), + li.getAttribute("data-name")]; + + if (radio.id === "radio_component_none_input") + components = undefined; + + break; + } + + if (components !== null) + set_page_components(components); + cancel_components(); +} + +function cancel_components() +{ + chbx_components_window.classList.add("hide"); + radio_components_window.classList.add("hide"); +} + +const UL_STATE = { + EDITING_ENTRY : 0, + ADDING_ENTRY : 1, + IDLE : 2 +}; + +const ul_by_prefix = { + [TYPE_PREFIX.PAGE] : { + ul : by_id("pages_ul"), + work_li : by_id("work_page_li"), + work_name_input : by_id("page_url_field"), + reset_work_li : reset_work_page_li, + get_work_li_data : work_page_li_data, + select_components : page_components, + commit_components : commit_page_components, + state : UL_STATE.IDLE, + edited_item : undefined, + }, + [TYPE_PREFIX.BAG] : { + ul : by_id("bags_ul"), + work_li : by_id("work_bag_li"), + work_name_input : by_id("bag_name_field"), + reset_work_li : reset_work_bag_li, + get_work_li_data : work_bag_li_data, + select_components : bag_components, + commit_components : commit_bag_components, + state : UL_STATE.IDLE, + edited_item : undefined, + }, + [TYPE_PREFIX.SCRIPT] : { + ul : by_id("scripts_ul"), + work_li : by_id("work_script_li"), + work_name_input : by_id("script_name_field"), + reset_work_li : reset_work_script_li, + get_work_li_data : work_script_li_data, + state : UL_STATE.IDLE, + edited_item : undefined, + } +} + +const import_window = by_id("import_window"); +const import_loading_radio = by_id("import_loading_radio"); +const import_failed_radio = by_id("import_failed_radio"); +const import_selection_radio = by_id("import_selection_radio"); +const bad_file_errormsg = by_id("bad_file_errormsg"); + +/* + * Newer browsers could utilise `text' method of File objects. + * Older ones require FileReader. + */ - const import_window = by_id("import_window"); - const import_loading_radio = by_id("import_loading_radio"); - const import_failed_radio = by_id("import_failed_radio"); - const import_selection_radio = by_id("import_selection_radio"); - const bad_file_errormsg = by_id("bad_file_errormsg"); +function _read_file(file, resolve, reject) +{ + let reader = new FileReader(); + + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(reader.error); + reader.readAsText(file); +} + +function read_file(file) +{ + return new Promise((resolve, reject) => + _read_file(file, resolve, reject)); +} + +async function import_from_file(event) +{ + let files = event.target.files; + if (files.length < 1) + return; + + import_window.classList.remove("hide"); + import_loading_radio.checked = true; + + let result = undefined; + + try { + result = JSON.parse(await read_file(files[0])); + } catch(e) { + bad_file_errormsg.textContent = "" + e; + import_failed_radio.checked = true; + return; + } + + let errormsg = validate_settings(result); + if (errormsg !== false) { + bad_file_errormsg.textContent = errormsg; + import_failed_radio.checked = true; + return; + } + + populate_import_list(result); + import_selection_radio.checked = true; +} + +function validate_settings(settings) +{ + // TODO + return false; +} + +function import_li_id(prefix, item) +{ + return `ili_${prefix}_${item}`; +} + +let import_ul = by_id("import_ul"); +let import_chbxs_colliding = undefined; +let settings_import_map = undefined; + +function populate_import_list(settings) +{ + let old_children = import_ul.children; + while (old_children[0] !== undefined) + import_ul.removeChild(old_children[0]); + + import_chbxs_colliding = []; + settings_import_map = new Map(); + + for (let setting of settings) { + let [key, value] = Object.entries(setting)[0]; + let prefix = key[0]; + let name = key.substring(1); + add_import_li(prefix, name); + settings_import_map.set(key, value); + } +} + +function add_import_li(prefix, name) +{ + let li = import_li_template.cloneNode(true); + let name_span = li.firstElementChild; + let chbx = name_span.nextElementSibling; + let warning_span = chbx.nextElementSibling; + + li.setAttribute("data-prefix", prefix); + li.setAttribute("data-name", name); + li.id = import_li_id(prefix, name); + name_span.textContent = nice_name(prefix, name); + + if (storage.get(prefix, name) !== undefined) { + import_chbxs_colliding.push(chbx); + warning_span.textContent = "(will overwrite existing setting!)"; + } + + import_ul.appendChild(li); +} + +function check_all_imports() +{ + for (let li of import_ul.children) + li.firstElementChild.nextElementSibling.checked = true; +} + +function uncheck_all_imports() +{ + for (let li of import_ul.children) + li.firstElementChild.nextElementSibling.checked = false; +} + +function uncheck_colliding_imports() +{ + for (let chbx of import_chbxs_colliding) + chbx.checked = false; +} + +const file_opener_form = by_id("file_opener_form"); + +function hide_import_window() +{ + import_window.classList.add("hide"); + /* Let GC free some memory */ + import_chbxs_colliding = undefined; + settings_import_map = undefined; /* - * Newer browsers could utilise `text' method of File objects. - * Older ones require FileReader. + * Reset file <input>. Without this, a second attempt to import the same + * file would result in "change" event on happening on <input> element. */ + file_opener_form.reset(); +} - function _read_file(file, resolve, reject) - { - let reader = new FileReader(); +function commit_import() +{ + for (let li of import_ul.children) { + let chbx = li.firstElementChild.nextElementSibling; - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(reader.error); - reader.readAsText(file); - } - - function read_file(file) - { - return new Promise((resolve, reject) => - _read_file(file, resolve, reject)); - } + if (!chbx.checked) + continue; - async function import_from_file(event) - { - let files = event.target.files; - if (files.length < 1) - return; - - import_window.classList.remove("hide"); - import_loading_radio.checked = true; - - let result = undefined; - - try { - result = JSON.parse(await read_file(files[0])); - } catch(e) { - bad_file_errormsg.textContent = "" + e; - import_failed_radio.checked = true; - return; - } - - let errormsg = validate_settings(result); - if (errormsg !== false) { - bad_file_errormsg.textContent = errormsg; - import_failed_radio.checked = true; - return; + let prefix = li.getAttribute("data-prefix"); + let name = li.getAttribute("data-name"); + let key = prefix + name; + let value = settings_import_map.get(key); + storage.set(prefix, name, value); + } + + hide_import_window(); +} + +function initialize_import_facility() +{ + let import_but = by_id("import_but"); + let file_opener = by_id("file_opener"); + let import_failok_but = by_id("import_failok_but"); + let check_all_import_but = by_id("check_all_import_but"); + let uncheck_all_import_but = by_id("uncheck_all_import_but"); + let uncheck_existing_import_but = by_id("uncheck_existing_import_but"); + let commit_import_but = by_id("commit_import_but"); + let cancel_import_but = by_id("cancel_import_but"); + import_but.addEventListener("click", () => file_opener.click()); + file_opener.addEventListener("change", import_from_file); + import_failok_but.addEventListener("click", hide_import_window); + check_all_import_but.addEventListener("click", check_all_imports); + uncheck_all_import_but.addEventListener("click", uncheck_all_imports); + uncheck_colliding_import_but + .addEventListener("click", uncheck_colliding_imports); + commit_import_but.addEventListener("click", commit_import); + cancel_import_but.addEventListener("click", hide_import_window); +} + +async function main() +{ + storage = await get_remote_storage(); + + for (let prefix of list_prefixes) { + for (let item of storage.get_all_names(prefix).sort()) { + add_li(prefix, item, true); + add_chbx_li(prefix, item); + add_radio_li(prefix, item); } - populate_import_list(result); - import_selection_radio.checked = true; - } - - function validate_settings(settings) - { - // TODO - return false; - } + let name = TYPE_NAME[prefix]; - function import_li_id(prefix, item) - { - return `ili_${prefix}_${item}`; - } + let add_but = by_id(`add_${name}_but`); + let discard_but = by_id(`discard_${name}_but`); + let save_but = by_id(`save_${name}_but`); - let import_ul = by_id("import_ul"); - let import_chbxs_colliding = undefined; - let settings_import_map = undefined; - - function populate_import_list(settings) - { - let old_children = import_ul.children; - while (old_children[0] !== undefined) - import_ul.removeChild(old_children[0]); - - import_chbxs_colliding = []; - settings_import_map = new Map(); - - for (let setting of settings) { - let [key, value] = Object.entries(setting)[0]; - let prefix = key[0]; - let name = key.substring(1); - add_import_li(prefix, name); - settings_import_map.set(key, value); - } - } + add_but.addEventListener("click", () => add_new_item(prefix)); + discard_but.addEventListener("click", () => cancel_work(prefix)); + save_but.addEventListener("click", () => save_work(prefix)); - function add_import_li(prefix, name) - { - let li = import_li_template.cloneNode(true); - let name_span = li.firstElementChild; - let chbx = name_span.nextElementSibling; - let warning_span = chbx.nextElementSibling; + if (prefix === TYPE_PREFIX.SCRIPT) + continue; - li.setAttribute("data-prefix", prefix); - li.setAttribute("data-name", name); - li.id = import_li_id(prefix, name); - name_span.textContent = nice_name(prefix, name); - - if (storage.get(prefix, name) !== undefined) { - import_chbxs_colliding.push(chbx); - warning_span.textContent = "(will overwrite existing setting!)"; - } + let ul = ul_by_prefix[prefix]; - import_ul.appendChild(li); - } + let commit_components_but = by_id(`commit_${name}_components_but`); + let cancel_components_but = by_id(`cancel_${name}_components_but`); + let select_components_but = by_id(`select_${name}_components_but`); - function check_all_imports() - { - for (let li of import_ul.children) - li.firstElementChild.nextElementSibling.checked = true; + commit_components_but + .addEventListener("click", ul.commit_components); + select_components_but + .addEventListener("click", ul.select_components); + cancel_components_but.addEventListener("click", cancel_components); } - function uncheck_all_imports() - { - for (let li of import_ul.children) - li.firstElementChild.nextElementSibling.checked = false; - } - - function uncheck_colliding_imports() - { - for (let chbx of import_chbxs_colliding) - chbx.checked = false; - } + initialize_import_facility(); - const file_opener_form = by_id("file_opener_form"); + storage.add_change_listener(handle_change); +} - function hide_import_window() - { - import_window.classList.add("hide"); - /* Let GC free some memory */ - import_chbxs_colliding = undefined; - settings_import_map = undefined; +function handle_change(change) +{ + if (change.old_val === undefined) { + add_li(change.prefix, change.item); + add_chbx_li(change.prefix, change.item); + add_radio_li(change.prefix, change.item); - /* - * Reset file <input>. Without this, a second attempt to import the same - * file would result in "change" event on happening on <input> element. - */ - file_opener_form.reset(); + return; } - function commit_import() - { - for (let li of import_ul.children) { - let chbx = li.firstElementChild.nextElementSibling; - - if (!chbx.checked) - continue; - - let prefix = li.getAttribute("data-prefix"); - let name = li.getAttribute("data-name"); - let key = prefix + name; - let value = settings_import_map.get(key); - storage.set(prefix, name, value); - } - - hide_import_window(); - } + if (change.new_val !== undefined) + return; - function initialize_import_facility() - { - let import_but = by_id("import_but"); - let file_opener = by_id("file_opener"); - let import_failok_but = by_id("import_failok_but"); - let check_all_import_but = by_id("check_all_import_but"); - let uncheck_all_import_but = by_id("uncheck_all_import_but"); - let uncheck_existing_import_but = by_id("uncheck_existing_import_but"); - let commit_import_but = by_id("commit_import_but"); - let cancel_import_but = by_id("cancel_import_but"); - import_but.addEventListener("click", () => file_opener.click()); - file_opener.addEventListener("change", import_from_file); - import_failok_but.addEventListener("click", hide_import_window); - check_all_import_but.addEventListener("click", check_all_imports); - uncheck_all_import_but.addEventListener("click", uncheck_all_imports); - uncheck_colliding_import_but - .addEventListener("click", uncheck_colliding_imports); - commit_import_but.addEventListener("click", commit_import); - cancel_import_but.addEventListener("click", hide_import_window); + let ul = ul_by_prefix[change.prefix]; + if (ul.state === UL_STATE.EDITING_ENTRY && + ul.edited_item === change.item) { + ul.state = UL_STATE.ADDING_ENTRY; + return; } - async function main() - { - storage = await get_storage(); - - for (let prefix of list_prefixes) { - for (let item of storage.get_all_names(prefix).sort()) { - add_li(prefix, item, true); - add_chbx_li(prefix, item); - add_radio_li(prefix, item); - } - - let name = TYPE_NAME[prefix]; - - let add_but = by_id(`add_${name}_but`); - let discard_but = by_id(`discard_${name}_but`); - let save_but = by_id(`save_${name}_but`); - - add_but.addEventListener("click", () => add_new_item(prefix)); - discard_but.addEventListener("click", () => cancel_work(prefix)); - save_but.addEventListener("click", () => save_work(prefix)); - - if (prefix === TYPE_PREFIX.SCRIPT) - continue; - - let ul = ul_by_prefix[prefix]; - - let commit_components_but = by_id(`commit_${name}_components_but`); - let cancel_components_but = by_id(`cancel_${name}_components_but`); - let select_components_but = by_id(`select_${name}_components_but`); - - commit_components_but - .addEventListener("click", ul.commit_components); - select_components_but - .addEventListener("click", ul.select_components); - cancel_components_but.addEventListener("click", cancel_components); - } - - initialize_import_facility(); + let uls_creators = [[ul.ul, item_li_id]]; - storage.add_change_listener(handle_change); + if (change.prefix !== TYPE_PREFIX.PAGE) { + uls_creators.push([chbx_components_ul, chbx_li_id]); + uls_creators.push([radio_components_ul, radio_li_id]); } - function handle_change(change) - { - if (change.old_val === undefined) { - add_li(change.prefix, change.item); - add_chbx_li(change.prefix, change.item); - add_radio_li(change.prefix, change.item); - - return; - } - - if (change.new_val !== undefined) - return; - - let ul = ul_by_prefix[change.prefix]; - if (ul.state === UL_STATE.EDITING_ENTRY && - ul.edited_item === change.item) { - ul.state = UL_STATE.ADDING_ENTRY; - return; - } - - let uls_creators = [[ul.ul, item_li_id]]; - - if (change.prefix !== TYPE_PREFIX.PAGE) { - uls_creators.push([chbx_components_ul, chbx_li_id]); - uls_creators.push([radio_components_ul, radio_li_id]); - } - - for (let [components_ul, id_creator] of uls_creators) { - let li = by_id(id_creator(change.prefix, change.item)); - components_ul.removeChild(li); - } + for (let [components_ul, id_creator] of uls_creators) { + let li = by_id(id_creator(change.prefix, change.item)); + components_ul.removeChild(li); } +} - main(); -})(); +main(); diff --git a/manifest.json b/manifest.json index 3984dc6..ab74523 100644 --- a/manifest.json +++ b/manifest.json @@ -1,31 +1,12 @@ // Copyright (C) 2021 Wojtek Kosior -// Copyright (C) 2021 jahoti // Redistribution terms are gathered in the `copyright' file. { "manifest_version": 2, "name": "My extension", "short_name": "Myext", - "version": "0.0.0", - - // WARNING!!! - // EACH USER SHOULD REPLACE "key" WITH UNIQUE VALUE!!! - // OTHERWISE SECURITY CAN BE TRIVIALLY COMPROMISED! - // - // A unique key can be generated with: - // $ ssh-keygen -f /path/to/new/key.pem -t rsa -b 1024 - // - // Only relevant to users of chrome-based browsers. - // Users of FireFox forks are safe. - - "key": "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcnNhAAAAAwEAAQAAAIEA+0GT5WNmRRo8e5tL9+BmNtY6aBPwLIgbPnLShYBMSR40iYwLTsccrkwBXb3bs1o4p6q5WJugI8Lsia+GXZc/XHGFkq7D1aWiTxlJLs8z0JC2TQ2/yatYmBMchogYGeeUfP7aI7JJZwpATts+VhIvgga/4FYj+DijMIEpwdckqFEAAAII4Dh7HOA4exwAAAAHc3NoLXJzYQAAAIEA+0GT5WNmRRo8e5tL9+BmNtY6aBPwLIgbPnLShYBMSR40iYwLTsccrkwBXb3bs1o4p6q5WJugI8Lsia+GXZc/XHGFkq7D1aWiTxlJLs8z0JC2TQ2/yatYmBMchogYGeeUfP7aI7JJZwpATts+VhIvgga/4FYj+DijMIEpwdckqFEAAAADAQABAAAAgEHB5/MhEKMFOs8e1cMJ97ZiWubiUPlWpcqyQmauLUj1nspg3JTBh8AWJEVkaxuFgU5gYCHQmRjC6yUdywyziOEkFA4r/WpX4WmbIe+GQHRHhitLN0dgF8N6/fVNOoa5StTdfZqyl23pVXyepoDNjrJFKyupqPMmpwfH5lGr9RwBAAAAQG76HflB/5j8P2YgIYX6dQT4Ei0SqiIjNVy7jFJUQDKSJg/PYkedE02JZJBJPcMYxEJUxXtMgq+upamNILfkmY0AAABBAP4v0O5dqjy16xDDFzb4DPNAcw5Za9KJaXKVkUuKXMNZOKTR0RC/upjNTmttY980RKdIx5zA25dO8cx563bSDIsAAABBAP0MaOpBiai/eRmLqhlthHODa+Mur6W3uc9PyhWhgDBjLNMR/doaYeyfVKxtIiN3a+HkN++G+vbokRweQv++bhMAAAANdXJ6QGxvY2FsaG9zdAECAwQFBg==", + "version": "0.0.0",_CHROMIUM_KEY_ "author": "various", - "description": "Kill the web&js", - "applications": { - "gecko": { - "id": "{6fe13369-88e9-440f-b837-5012fb3bedec}", - "strict_min_version": "60.0" - } - }, + "description": "Kill the web&js",_GECKO_APPLICATIONS_ "icons":{ "64": "icons/myext.png" }, @@ -56,23 +37,7 @@ ], "background": { "persistent": true, - "scripts": [ - "common/stored_types.js", - "common/lock.js", - "common/once.js", - "common/browser.js", - "background/storage.js", - "background/message_server.js", - "common/connection_types.js", - "background/storage_server.js", - "common/url_item.js", - "common/sha256.js", - "background/settings_query.js", - "background/page_actions_server.js", - "common/gen_unique.js", - "background/policy_injector.js", - "background/main.js" - ] + "scripts": [_BGSCRIPTS_] }, "content_scripts": [ { @@ -80,16 +45,7 @@ "matches": ["<all_urls>"], "match_about_blank": true, "all_frames": true, - "js": [ - "content/freezer.js", - "common/browser.js", - "common/connection_types.js", - "content/page_actions.js", - "common/url_item.js", - "common/sha256.js", - "common/gen_unique.js", - "content/main.js" - ] + "js": [_CONTENTSCRIPTS_] } ] } |