Project

General

Profile

« Previous | Next » 

Revision 261548ff

Added by koszko about 2 years ago

emply an sh-based build system; make some changes to blocking

View differences:

README.txt
6 6
without executing nonfree software.
7 7

  
8 8
Currently, the target browsers for this extension are Ungoogled Chromium
9
and various forks of Firefox Quantum (57+).
9
and various forks of Firefox (version 60+).
10 10

  
11 11
This extension is still in an early stage. See TODOS.org. Also see
12 12
`https://git.koszko.org/browser-extension-doc/' for documentation in
13 13
development.
14 14

  
15 15
## Installation ##
16
The extension can be loaded into Ungoogled Chromium or a modern Gecko-based
17
browser as unpacked extension. As of now, project's main directory is also
18
the extension directory.
16
The extension can be "built" with `./build.sh mozilla' or `./build.sh chromium'.
17
This creates directories build_mozilla/ and  build_chromium/, respectively.
18
Such directory can be loaded into Ungoogled Chromium or a modern Gecko-based
19
browser as unpacked extension.
19 20

  
20 21
## Copyright ##
21 22
All copyright information is gathered in the `copyright' file which follows
TODOS.org
20 20
- make script bag components re-orderable (via drag&drop in options page) -- CRUCIAL
21 21
- find some way not to require each chrome user to modify manifest.json
22 22
- test with more browser forks (Abrowser, Parabola IceWeasel, LibreWolf)
23
  - also see if browsers based on pre-quantum FF support enough of
24
    WebExtensions for easy porting
25 23
- make sure page's own csp in <head> doesn't block our scripts
26
- find out how and make it possible to whitelist non-https urls and
27
  whether we can inject csp to them
28 24
- create a repository to host scripts
29 25
  - enable the extension to automatically fetch script substitutes from the repo
30 26
- make it possible to inject scripts to arbitrary places in DOM
31 27
  - make script blocking code omit those scripts
32 28
- check if prerendering has to be blocked -- CRUCIAL
33 29
- block prefetch
34
- rearrange files in extension, add some mechanism to build the extension
35
- all solutions to modularize js code SUCK; come up with own simple DSL
36
  to manage imports/exports
30
- rearrange files in extension
31
- supplement the build script with a makefile, also produce zipped arifacts
37 32
- perform never-ending refactoring of already-written code
38 33
- also implement support for whitelisting of non-https urls
39 34
- validate data entered in settings
......
49 44
    (unless someone suggests another good name before we do so)
50 45
- add an option to disable script blocking globally
51 46
- Add support to settings_query for non-standard URLs
52
  (e.g. file:// and about:)
47
  (e.g. file:// and ftp://)
53 48
- Process HTML files in data: URLs instead of just blocking them
49
- improve CSP injection for pathological cases like <script> before <head>
50
- Fix FF script blocking and whitelisting (FF seems to be by itself repeatedly
51
  injecting CSP headers that were injected once, this makes it impossible to
52
  whielist site that was unwhitelisted before; FF also seems to be removing our
53
  injected script's nonce for no reason 🙁)
54 54

  
55 55
DONE:
56
- make blocking more torough -- DONE 2021-06-28
56
- find out if we can successfully use CSP to block file:// under FF -- DONE 2021-06-30
57
- come up with own simple DSL to manage imports/exports -- DONE 2021-06-30
58
- add some mechanism to build the extension -- DONE 2021-06-30
59
- see if browsers based on pre-quantum FF support enough of -- DONE 2021-06-29
60
  WebExtensions for easy porting (no, those we know dropped the support)
61
- make blocking more thorough -- DONE 2021-06-28
57 62
  - mind the data: urls -- CRUCIAL
58 63
- employ copyright file in Debian format -- DONE 2021-06-25
59 64
- find out what causes storage sometimes not to get initialized under IceCat 60 -- DONE 2021-06-23
background/main.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
9

  
10
(() => {
11
    const TYPE_PREFIX = window.TYPE_PREFIX;
12
    const get_storage = window.get_storage;
13
    const start_storage_server = window.start_storage_server;
14
    const start_page_actions_server = window.start_page_actions_server;
15
    const start_policy_injector = window.start_policy_injector;
16
    const browser = window.browser;
17

  
18
    start_storage_server();
19
    start_page_actions_server();
20
    start_policy_injector();
21

  
22
    async function init_myext(install_details)
23
    {
24
	console.log("details:", install_details);
25
	if (install_details.reason != "install")
26
	    return;
27

  
28
	let storage = await get_storage();
29

  
30
	await storage.clear();
31

  
32
	/*
33
	 * Below we add sample settings to the extension.
34
	 * Those should be considered example values for viewing in the options
35
	 * page. They won't make my.fsf.org work. The only scripts that does
36
	 * something useful right now is the opencores one.
37
	 */
38

  
39
	let components = [];
40
	for (let script_data of [
41
	    {url: "http://127.0.0.1:8000/myfsf_define_CRM.js",
42
	     hash:"bf0cc81c7e8d5f800877b4bc3f14639f946f5ac6d4dc120255ffac5eba5e48fe"},
43
	    {url: "https://my.fsf.org/misc/jquery.js?v=1.4.4",
44
	     hash:"261ae472fa0cbf27c80c9200a1599a60fde581a0e652eee4bf41def8cb61f2d0"},
45
	    {url: "https://my.fsf.org/misc/jquery-extend-3.4.0.js?v=1.4.4",
46
	     hash:"c54103ba57ee210ca55c052e70415402707548a4e6a68dd6efb3895019bee392"},
47
	    {url: "https://my.fsf.org/misc/jquery-html-prefilter-3.5.0-backport.js?v=1.4.4",
48
	     hash:"fad84efa145fb507e5df9b582fa01b1c4e6313de7f72ebdd55726d92fa4dbf06"},
49
	    {url: "https://my.fsf.org/misc/jquery.once.js?v=1.2",
50
	     hash:"1430f42c0d760ba8e05bb3762480502e541f654fec5739ee40625ab22dc38c4f"},
51
	    {url: "https://my.fsf.org/misc/drupal.js?qmaukd",
52
	     hash:"2e08dccbd4d8b728a6871562995a4636b89bfe0ed3b8fb0138191c922228b116"},
53
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery/dist/jquery.min.js?qmaukd",
54
	     hash:"a6d01520d28d15dbe476de84eea90eb3ee2d058722efc062ec73cb5fad78a17b"},
55
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-ui/jquery-ui.min.js?qmaukd",
56
	     hash:"28ce75d953678c4942df47a11707a15e3c756021cf89090e3e6aa7ad6b6971c3"},
57
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/lodash-compat/lodash.min.js?qmaukd",
58
	     hash:"f2871cc80c52fe8c04c582c4a49797c9c8fd80391cf1452e47f7fe97835ed5cc"},
59
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.mousewheel.min.js?qmaukd",
60
	     hash:"f50233e84c2ac7ada37a094d3f7d3b3f7c97716d6b7b47bf69619d93ee4ac1ce"},
61
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/select2/select2.min.js?qmaukd",
62
	     hash:"ce61298fb9aa4ec49ccd4172d097e36a9e5db3af06a7b82796659368f15b7c1b"},
63
	    {url: "https://my.fsf.org/sites/all/modules/civi crm/packages/jquery/plugins/jquery.form.min.js?qmaukd",
64
	     hash:"c90f0e501d2948fbc2b61bffd654fa4ab64741fd48923782419eeb14d3816fb8"},
65
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.timeentry.min.js?qmaukd",
66
	     hash:"8e85df981e8ad7049d06dfb075277d038734d36a7097c7f021021b2bdccfe9bb"},
67
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.blockUI.min.js?qmaukd",
68
	     hash:"806aedff52ac822f2adc5797073e1e5c5cec32eb9f15f2319cb32a347dcd232b"},
69
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/datatables/media/js/jquery.dataTables.min.js?qmaukd",
70
	     hash:"b796504d9b1b422f0dc6ccc2d740ac78a8c9e5078cc3934836d39742b1121925"},
71
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-validation/dist/jquery.validate.min.js?qmaukd",
72
	     hash:"f0f5373ad203101ea91bf826c5a7ef8f7cd74887f06bad2cb9277a504503b9e2"},
73
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.ui.datepicker.validation.min.js?qmaukd",
74
	     hash:"c6e6f6bf7f8fff25cca338045774e267e8eaa2d48ac9100540f3d59a6d2b3c61"},
75
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/js/Common.js?qmaukd",
76
	     hash:"17aa222a3af2e8958be16accb5e77ef39f67009cb3b500718d8fffd45b399148"},
77
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.datepicker.js?qmaukd",
78
	     hash:"9bd8d10208aa99c156325f7819da6f0dd62ba221ac4119c3ccd4834e2cf36535"},
79
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.ajax.js?qmaukd",
80
	     hash:"6401a4e257b7499ae4a00be2c200e4504a2c9b3d6b278a830c31a7b63374f0fe"},
81
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/js/wysiwyg/crm.wysiwyg.js?qmaukd",
82
	     hash:"fa962356072a36672c3b4b25bdeb657f020995a067e20a29cd5bb84b05157762"},
83
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/js/noconflict.js?qmaukd",
84
	     hash:"58d6d9f496a235d23cf891926d71f2104e4f2afe1d14bb4e2b5233f646c35e62"},
85
	    {url: "https://my.fsf.org/sites/all/modules/matomo/matomo.js?qmaukd",
86
	     hash:"7f39ccd085f348189cd2fb62ea4d4a658d96f6bba266265880b98605e777e2de"},
87
	    {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/global.js?qmaukd",
88
	     hash:"aa7983f6b902f9f4415cfc8346e0c3f194cc95b78f52f2ad09ec7effa1326b9c"},
89
	    {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.superfish.min.js?qmaukd",
90
	     hash:"5ef1f93bf3901227056bf9ed0ed93a148eec4dda30f419756b12bedd1098815e"},
91
	    {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.sidr.min.js?qmaukd",
92
	     hash:"c4914d415826676c6af2e61f16edb72c5388f8600ba6de9049892aee49d980a0"},
93
	    {url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.flexslider.min.js?qmaukd",
94
	     hash:"cefaf715761b4494913851249b9d40dacb4a8cb61242b0efc859dc586d56e0d4"},
95
	    {url: "http://127.0.0.1:8000/myfsf_crap.js",
96
	     hash:"d91ccf21592d0f861ea0ba946bc257fc5d88269327cad0a91387da6cb8ff633e"},
97
	    {url: "https://my.fsf.org/sites/all/modules/civicrm/templates/CRM/Core/BillingBlock.js?r=lp7Di",
98
	     hash:"2f25d35e7a0c0060ab0a444a577f09dd3c9934ae898a7ee0eb20b6c986ab5a1c"},
99
	    {url: "https://my.fsf.org/extensions/com.aghstrategies.giftmemberships/js/giftpricefield.js?r=lp7Di",
100
	     hash:"f86080e6bd306fe46474039aeca2808235005bce5a2a29416d08210022039a45"},
101
	    {url: "https://my.fsf.org/extensions/com.ginkgostreet.negativenegator/js/negativenegator.js?r=lp7Di",
102
	     hash:"d0e87bac832856db70947d82a7ab4e0b7c8b1070d5f1a32335345e033ece3a14"}
103
	]) {
104
	    let name_regex = /\/([^/]+)\.js/;
105
	    let name = name_regex.exec(script_data.url)[1];
106
	    await storage.set(TYPE_PREFIX.SCRIPT, name, script_data);
107
	    components.push([TYPE_PREFIX.SCRIPT, name]);
108
	}
109

  
110
	await storage.set(TYPE_PREFIX.BAG, "myfsf_join", components);
111

  
112
	await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/join", {
113
	    components: [TYPE_PREFIX.BAG, "myfsf_join"]
114
	});
115

  
116
	let hello_script = {
117
	    text: "console.log(\"hello, every1!\");\n"
118
	};
119
	await storage.set(TYPE_PREFIX.SCRIPT, "hello", hello_script);
120
	await storage.set(TYPE_PREFIX.BAG, "hello",
121
			  [[TYPE_PREFIX.SCRIPT, "hello"]]);
122
	await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/", {
123
	    components: [TYPE_PREFIX.BAG, "hello"],
124
	    allow: true
125
	});
126

  
127
	let opencores_script = {
128
	    text: `\
8
/*
9
 * IMPORTS_START
10
 * IMPORT TYPE_PREFIX
11
 * IMPORT get_storage
12
 * IMPORT start_storage_server
13
 * IMPORT start_page_actions_server
14
 * IMPORT start_policy_injector
15
 * IMPORT browser
16
 * IMPORTS_END
17
 */
18

  
19
start_storage_server();
20
start_page_actions_server();
21
start_policy_injector();
22

  
23
async function init_myext(install_details)
24
{
25
    console.log("details:", install_details);
26
    if (install_details.reason != "install")
27
	return;
28

  
29
    let storage = await get_storage();
30

  
31
    await storage.clear();
32

  
33
    /*
34
     * Below we add sample settings to the extension.
35
     * Those should be considered example values for viewing in the options
36
     * page. They won't make my.fsf.org work. The only scripts that does
37
     * something useful right now is the opencores one.
38
     */
39

  
40
    let components = [];
41
    for (let script_data of [
42
	{url: "http://127.0.0.1:8000/myfsf_define_CRM.js",
43
	 hash:"bf0cc81c7e8d5f800877b4bc3f14639f946f5ac6d4dc120255ffac5eba5e48fe"},
44
	{url: "https://my.fsf.org/misc/jquery.js?v=1.4.4",
45
	 hash:"261ae472fa0cbf27c80c9200a1599a60fde581a0e652eee4bf41def8cb61f2d0"},
46
	{url: "https://my.fsf.org/misc/jquery-extend-3.4.0.js?v=1.4.4",
47
	 hash:"c54103ba57ee210ca55c052e70415402707548a4e6a68dd6efb3895019bee392"},
48
	{url: "https://my.fsf.org/misc/jquery-html-prefilter-3.5.0-backport.js?v=1.4.4",
49
	 hash:"fad84efa145fb507e5df9b582fa01b1c4e6313de7f72ebdd55726d92fa4dbf06"},
50
	{url: "https://my.fsf.org/misc/jquery.once.js?v=1.2",
51
	 hash:"1430f42c0d760ba8e05bb3762480502e541f654fec5739ee40625ab22dc38c4f"},
52
	{url: "https://my.fsf.org/misc/drupal.js?qmaukd",
53
	 hash:"2e08dccbd4d8b728a6871562995a4636b89bfe0ed3b8fb0138191c922228b116"},
54
	{url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery/dist/jquery.min.js?qmaukd",
55
	 hash:"a6d01520d28d15dbe476de84eea90eb3ee2d058722efc062ec73cb5fad78a17b"},
56
	{url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-ui/jquery-ui.min.js?qmaukd",
57
	 hash:"28ce75d953678c4942df47a11707a15e3c756021cf89090e3e6aa7ad6b6971c3"},
58
	{url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/lodash-compat/lodash.min.js?qmaukd",
59
	 hash:"f2871cc80c52fe8c04c582c4a49797c9c8fd80391cf1452e47f7fe97835ed5cc"},
60
	{url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.mousewheel.min.js?qmaukd",
61
	 hash:"f50233e84c2ac7ada37a094d3f7d3b3f7c97716d6b7b47bf69619d93ee4ac1ce"},
62
	{url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/select2/select2.min.js?qmaukd",
63
	 hash:"ce61298fb9aa4ec49ccd4172d097e36a9e5db3af06a7b82796659368f15b7c1b"},
64
	{url: "https://my.fsf.org/sites/all/modules/civi crm/packages/jquery/plugins/jquery.form.min.js?qmaukd",
65
	 hash:"c90f0e501d2948fbc2b61bffd654fa4ab64741fd48923782419eeb14d3816fb8"},
66
	{url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.timeentry.min.js?qmaukd",
67
	 hash:"8e85df981e8ad7049d06dfb075277d038734d36a7097c7f021021b2bdccfe9bb"},
68
	{url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.blockUI.min.js?qmaukd",
69
	 hash:"806aedff52ac822f2adc5797073e1e5c5cec32eb9f15f2319cb32a347dcd232b"},
70
	{url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/datatables/media/js/jquery.dataTables.min.js?qmaukd",
71
	 hash:"b796504d9b1b422f0dc6ccc2d740ac78a8c9e5078cc3934836d39742b1121925"},
72
	{url: "https://my.fsf.org/sites/all/modules/civicrm/bower_components/jquery-validation/dist/jquery.validate.min.js?qmaukd",
73
	 hash:"f0f5373ad203101ea91bf826c5a7ef8f7cd74887f06bad2cb9277a504503b9e2"},
74
	{url: "https://my.fsf.org/sites/all/modules/civicrm/packages/jquery/plugins/jquery.ui.datepicker.validation.min.js?qmaukd",
75
	 hash:"c6e6f6bf7f8fff25cca338045774e267e8eaa2d48ac9100540f3d59a6d2b3c61"},
76
	{url: "https://my.fsf.org/sites/all/modules/civicrm/js/Common.js?qmaukd",
77
	 hash:"17aa222a3af2e8958be16accb5e77ef39f67009cb3b500718d8fffd45b399148"},
78
	{url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.datepicker.js?qmaukd",
79
	 hash:"9bd8d10208aa99c156325f7819da6f0dd62ba221ac4119c3ccd4834e2cf36535"},
80
	{url: "https://my.fsf.org/sites/all/modules/civicrm/js/crm.ajax.js?qmaukd",
81
	 hash:"6401a4e257b7499ae4a00be2c200e4504a2c9b3d6b278a830c31a7b63374f0fe"},
82
	{url: "https://my.fsf.org/sites/all/modules/civicrm/js/wysiwyg/crm.wysiwyg.js?qmaukd",
83
	 hash:"fa962356072a36672c3b4b25bdeb657f020995a067e20a29cd5bb84b05157762"},
84
	{url: "https://my.fsf.org/sites/all/modules/civicrm/js/noconflict.js?qmaukd",
85
	 hash:"58d6d9f496a235d23cf891926d71f2104e4f2afe1d14bb4e2b5233f646c35e62"},
86
	{url: "https://my.fsf.org/sites/all/modules/matomo/matomo.js?qmaukd",
87
	 hash:"7f39ccd085f348189cd2fb62ea4d4a658d96f6bba266265880b98605e777e2de"},
88
	{url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/global.js?qmaukd",
89
	 hash:"aa7983f6b902f9f4415cfc8346e0c3f194cc95b78f52f2ad09ec7effa1326b9c"},
90
	{url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.superfish.min.js?qmaukd",
91
	 hash:"5ef1f93bf3901227056bf9ed0ed93a148eec4dda30f419756b12bedd1098815e"},
92
	{url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.sidr.min.js?qmaukd",
93
	 hash:"c4914d415826676c6af2e61f16edb72c5388f8600ba6de9049892aee49d980a0"},
94
	{url: "https://my.fsf.org/sites/all/themes/fsf_venture/js/jquery.flexslider.min.js?qmaukd",
95
	 hash:"cefaf715761b4494913851249b9d40dacb4a8cb61242b0efc859dc586d56e0d4"},
96
	{url: "http://127.0.0.1:8000/myfsf_crap.js",
97
	 hash:"d91ccf21592d0f861ea0ba946bc257fc5d88269327cad0a91387da6cb8ff633e"},
98
	{url: "https://my.fsf.org/sites/all/modules/civicrm/templates/CRM/Core/BillingBlock.js?r=lp7Di",
99
	 hash:"2f25d35e7a0c0060ab0a444a577f09dd3c9934ae898a7ee0eb20b6c986ab5a1c"},
100
	{url: "https://my.fsf.org/extensions/com.aghstrategies.giftmemberships/js/giftpricefield.js?r=lp7Di",
101
	 hash:"f86080e6bd306fe46474039aeca2808235005bce5a2a29416d08210022039a45"},
102
	{url: "https://my.fsf.org/extensions/com.ginkgostreet.negativenegator/js/negativenegator.js?r=lp7Di",
103
	 hash:"d0e87bac832856db70947d82a7ab4e0b7c8b1070d5f1a32335345e033ece3a14"}
104
    ]) {
105
	let name_regex = /\/([^/]+)\.js/;
106
	let name = name_regex.exec(script_data.url)[1];
107
	await storage.set(TYPE_PREFIX.SCRIPT, name, script_data);
108
	components.push([TYPE_PREFIX.SCRIPT, name]);
109
    }
110

  
111
    await storage.set(TYPE_PREFIX.BAG, "myfsf_join", components);
112

  
113
    await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/join", {
114
	components: [TYPE_PREFIX.BAG, "myfsf_join"]
115
    });
116

  
117
    let hello_script = {
118
	text: "console.log(\"hello, every1!\");\n"
119
    };
120
    await storage.set(TYPE_PREFIX.SCRIPT, "hello", hello_script);
121
    await storage.set(TYPE_PREFIX.BAG, "hello",
122
		      [[TYPE_PREFIX.SCRIPT, "hello"]]);
123
    await storage.set(TYPE_PREFIX.PAGE, "https://my.fsf.org/", {
124
	components: [TYPE_PREFIX.BAG, "hello"],
125
	allow: true
126
    });
127

  
128
    let opencores_script = {
129
	text: `\
129 130
let data = JSON.parse(document.getElementById("__NEXT_DATA__").textContent);
130 131
let sections = {};
131 132
for (let h1 of document.getElementsByClassName("cMJCrc")) {
......
154 155
    ul.appendChild(li);
155 156
}
156 157
`
157
	};
158
    };
158 159

  
159
	await storage.set(TYPE_PREFIX.SCRIPT, "opencores", opencores_script);
160
	await storage.set(TYPE_PREFIX.PAGE, "https://opencores.org/projects", {
161
	    components: [TYPE_PREFIX.SCRIPT, "opencores"],
162
	    allow: false
163
	});
164
    }
160
    await storage.set(TYPE_PREFIX.SCRIPT, "opencores", opencores_script);
161
    await storage.set(TYPE_PREFIX.PAGE, "https://opencores.org/projects", {
162
	components: [TYPE_PREFIX.SCRIPT, "opencores"],
163
	allow: false
164
    });
165
}
165 166

  
166
    browser.runtime.onInstalled.addListener(init_myext);
167
browser.runtime.onInstalled.addListener(init_myext);
167 168

  
168
    console.log("hello, myext");
169
})();
169
console.log("hello, myext");
background/message_server.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
9

  
10
(() => {
11
    const browser = window.browser;
8
/*
9
 * IMPORTS_START
10
 * IMPORT browser
11
 * IMPORTS_END
12
 */
12 13

  
13
    var listeners = {};
14
var listeners = {};
14 15

  
15
    /* magic should be one of the constants from /common/connection_types.js */
16
/* magic should be one of the constants from /common/connection_types.js */
16 17

  
17
    function listen_for_connection(magic, cb)
18
    {
19
	listeners[magic] = cb;
20
    }
18
function listen_for_connection(magic, cb)
19
{
20
    listeners[magic] = cb;
21
}
21 22

  
22
    function raw_listen(port) {
23
	if (listeners[port.name] === undefined)
24
	    return;
23
function raw_listen(port)
24
{
25
    if (listeners[port.name] === undefined)
26
	return;
25 27

  
26
	listeners[port.name](port);
27
    }
28
    listeners[port.name](port);
29
}
28 30

  
29
    browser.runtime.onConnect.addListener(raw_listen);
31
browser.runtime.onConnect.addListener(raw_listen);
30 32

  
31
    window.listen_for_connection = listen_for_connection;
32
})();
33
/*
34
 * EXPORTS_START
35
 * EXPORT listen_for_connection
36
 * EXPORTS_END
37
 */
background/page_actions_server.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
9

  
10
(() => {
11
    const get_storage = window.get_storage;
12
    const TYPE_PREFIX = window.TYPE_PREFIX;
13
    const CONNECTION_TYPE = window.CONNECTION_TYPE;
14
    const browser = window.browser;
15
    const listen_for_connection = window.listen_for_connection;
16
    const sha256 = window.sha256;
17
    const get_query_best = window.get_query_best;
18

  
19
    var storage;
20
    var query_best;
21
    var handler;
22

  
23
    function send_scripts(url, port)
24
    {
25
	let [pattern, settings] = query_best(url);
26
	if (settings === undefined)
27
	    return;
28

  
29
	let components = settings.components;
30
	let processed_bags = new Set();
31

  
32
	if (components !== undefined)
33
	    send_scripts_rec([components], port, processed_bags);
34
    }
8
/*
9
 * IMPORTS_START
10
 * IMPORT get_storage
11
 * IMPORT TYPE_PREFIX
12
 * IMPORT CONNECTION_TYPE
13
 * IMPORT browser
14
 * IMPORT listen_for_connection
15
 * IMPORT sha256
16
 * IMPORT get_query_best
17
 * IMPORTS_END
18
 */
35 19

  
36
    // TODO: parallelize script fetching
37
    async function send_scripts_rec(components, port, processed_bags)
38
    {
39
	for (let [prefix, name] of components) {
40
	    if (prefix === TYPE_PREFIX.BAG) {
41
		if (processed_bags.has(name)) {
42
		    console.log(`preventing recursive inclusion of bag ${name}`);
43
		    continue;
44
		}
45

  
46
		var bag = storage.get(TYPE_PREFIX.BAG, name);
47

  
48
		if (bag === undefined) {
49
		    console.log(`no bag in storage for key ${name}`);
50
		    continue;
51
		}
52

  
53
		processed_bags.add(name);
54
		await send_scripts_rec(bag, port, processed_bags);
55
		processed_bags.delete(name);
56
	    } else {
57
		let script_text = await get_script_text(name);
58
		if (script_text === undefined)
59
		    continue;
60

  
61
		port.postMessage({inject : [script_text]});
20
var storage;
21
var query_best;
22
var handler;
23

  
24
function send_scripts(url, port)
25
{
26
    let [pattern, settings] = query_best(url);
27
    if (settings === undefined)
28
	return;
29

  
30
    let components = settings.components;
31
    let processed_bags = new Set();
32

  
33
    if (components !== undefined)
34
	send_scripts_rec([components], port, processed_bags);
35
}
36

  
37
// TODO: parallelize script fetching
38
async function send_scripts_rec(components, port, processed_bags)
39
{
40
    for (let [prefix, name] of components) {
41
	if (prefix === TYPE_PREFIX.BAG) {
42
	    if (processed_bags.has(name)) {
43
		console.log(`preventing recursive inclusion of bag ${name}`);
44
		continue;
62 45
	    }
63
	}
64
    }
65 46

  
66
    async function get_script_text(script_name)
67
    {
68
	try {
69
	    let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name);
70
	    if (script_data === undefined) {
71
		console.log(`missing data for ${script_name}`);
72
		return;
47
	    var bag = storage.get(TYPE_PREFIX.BAG, name);
48

  
49
	    if (bag === undefined) {
50
		console.log(`no bag in storage for key ${name}`);
51
		continue;
73 52
	    }
74
	    let script_text = script_data.text;
75
	    if (!script_text)
76
		script_text = await fetch_remote_script(script_data);
77
	    return script_text;
78
	} catch (e) {
79
	    console.log(e);
80
	}
81
    }
82 53

  
83
    function ajax_callback()
84
    {
85
	if (this.readyState == 4)
86
	    this.resolve_callback(this);
87
    }
54
	    processed_bags.add(name);
55
	    await send_scripts_rec(bag, port, processed_bags);
56
	    processed_bags.delete(name);
57
	} else {
58
	    let script_text = await get_script_text(name);
59
	    if (script_text === undefined)
60
		continue;
88 61

  
89
    function initiate_ajax_request(resolve, method, url)
90
    {
91
	var xhttp = new XMLHttpRequest();
92
	xhttp.resolve_callback = resolve;
93
	xhttp.onreadystatechange = ajax_callback;
94
	xhttp.open(method, url, true);
95
	xhttp.send();
62
	    port.postMessage({inject : [script_text]});
63
	}
96 64
    }
97

  
98
    function make_ajax_request(method, url)
99
    {
100
	return new Promise((resolve, reject) =>
101
			   initiate_ajax_request(resolve, method, url));
65
}
66

  
67
async function get_script_text(script_name)
68
{
69
    try {
70
	let script_data = storage.get(TYPE_PREFIX.SCRIPT, script_name);
71
	if (script_data === undefined) {
72
	    console.log(`missing data for ${script_name}`);
73
	    return;
74
	}
75
	let script_text = script_data.text;
76
	if (!script_text)
77
	    script_text = await fetch_remote_script(script_data);
78
	return script_text;
79
    } catch (e) {
80
	console.log(e);
102 81
    }
103

  
104
    async function fetch_remote_script(script_data)
105
    {
106
	try {
107
	    let xhttp = await make_ajax_request("GET", script_data.url);
108
	    if (xhttp.status === 200) {
109
		let computed_hash = sha256(xhttp.responseText);
110
		if (computed_hash !== script_data.hash) {
111
		    console.log(`Bad hash for ${script_data.url}\n    got ${computed_hash} instead of ${script_data.hash}`);
112
		    return;
113
		}
114
		return xhttp.responseText;
115
	    } else {
116
		console.log("script not fetched: " + script_data.url);
82
}
83

  
84
function ajax_callback()
85
{
86
    if (this.readyState == 4)
87
	this.resolve_callback(this);
88
}
89

  
90
function initiate_ajax_request(resolve, method, url)
91
{
92
    var xhttp = new XMLHttpRequest();
93
    xhttp.resolve_callback = resolve;
94
    xhttp.onreadystatechange = ajax_callback;
95
    xhttp.open(method, url, true);
96
    xhttp.send();
97
}
98

  
99
function make_ajax_request(method, url)
100
{
101
    return new Promise((resolve, reject) =>
102
		       initiate_ajax_request(resolve, method, url));
103
}
104

  
105
async function fetch_remote_script(script_data)
106
{
107
    try {
108
	let xhttp = await make_ajax_request("GET", script_data.url);
109
	if (xhttp.status === 200) {
110
	    let computed_hash = sha256(xhttp.responseText);
111
	    if (computed_hash !== script_data.hash) {
112
		console.log(`Bad hash for ${script_data.url}\n    got ${computed_hash} instead of ${script_data.hash}`);
117 113
		return;
118 114
	    }
119
	} catch (e) {
120
	    console.log(e);
115
	    return xhttp.responseText;
116
	} else {
117
	    console.log("script not fetched: " + script_data.url);
118
	    return;
121 119
	}
120
    } catch (e) {
121
	console.log(e);
122 122
    }
123

  
124
    function handle_message(port, message, handler)
125
    {
126
	port.onMessage.removeListener(handler[0]);
127
	let url = message.url;
128
	console.log({url});
129
	send_scripts(url, port);
130
    }
131

  
132
    function new_connection(port)
133
    {
134
	console.log("new page actions connection!");
135
	let handler = [];
136
	handler.push(m => handle_message(port, m, handler));
137
	port.onMessage.addListener(handler[0]);
138
    }
139

  
140
    async function start()
141
    {
142
	storage = await get_storage();
143
	query_best = await get_query_best();
144

  
145
	listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
146
    }
147

  
148
    window.start_page_actions_server = start;
149
})();
123
}
124

  
125
function handle_message(port, message, handler)
126
{
127
    port.onMessage.removeListener(handler[0]);
128
    let url = message.url;
129
    console.log({url});
130
    send_scripts(url, port);
131
}
132

  
133
function new_connection(port)
134
{
135
    console.log("new page actions connection!");
136
    let handler = [];
137
    handler.push(m => handle_message(port, m, handler));
138
    port.onMessage.addListener(handler[0]);
139
}
140

  
141
async function start_page_actions_server()
142
{
143
    storage = await get_storage();
144
    query_best = await get_query_best();
145

  
146
    listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
147
}
148

  
149
/*
150
 * EXPORTS_START
151
 * EXPORT start_page_actions_server
152
 * EXPORTS_END
153
 */
background/policy_injector.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
8
/*
9
 * IMPORTS_START
10
 * IMPORT TYPE_PREFIX
11
 * IMPORT get_storage
12
 * IMPORT browser
13
 * IMPORT is_chrome
14
 * IMPORT gen_unique
15
 * IMPORT url_item
16
 * IMPORT get_query_best
17
 * IMPORTS_END
18
 */
9 19

  
10
(() => {
11
    const TYPE_PREFIX = window.TYPE_PREFIX;
12
    const get_storage = window.get_storage;
13
    const browser = window.browser;
14
    const is_chrome = window.is_chrome;
15
    const gen_unique = window.gen_unique;
16
    const url_item = window.url_item;
17
    const get_query_best = window.get_query_best;
20
var storage;
21
var query_best;
18 22

  
19
    var storage;
20
    var query_best;
23
let csp_header_names = {
24
    "content-security-policy" : true,
25
    "x-webkit-csp" : true,
26
    "x-content-security-policy" : true
27
};
21 28

  
22
    let csp_header_names = {
23
	"content-security-policy" : true,
24
	"x-webkit-csp" : true,
25
	"x-content-security-policy" : true
26
    };
29
function is_noncsp_header(header)
30
{
31
    return !csp_header_names[header.name.toLowerCase()];
32
}
27 33

  
28
    function is_noncsp_header(header)
29
    {
30
	return !csp_header_names[header.name.toLowerCase()];
31
    }
34
function inject(details)
35
{
36
    let url = url_item(details.url);
32 37

  
33
    function inject(details)
34
    {
35
	let url = url_item(details.url);
38
    let [pattern, settings] = query_best(url);
36 39

  
37
	let [pattern, settings] = query_best(url);
40
    if (settings !== undefined && settings.allow)
41
	return {cancel : false};
38 42

  
39
	if (settings !== undefined && settings.allow) {
40
	    console.log("allowing", url);
41
	    return {cancel : false};
42
	}
43
    let nonce = gen_unique(url).substring(1);
44
    let headers = details.responseHeaders.filter(is_noncsp_header);
43 45

  
44
	let nonce = gen_unique(url).substring(1);
45
	let headers = details.responseHeaders.filter(is_noncsp_header);
46
	headers.push({
47
	    name : "content-security-policy",
48
	    value : `script-src 'nonce-${nonce}'; script-src-elem 'nonce-${nonce}';`
49
	});
46
    let rule = `script-src 'nonce-${nonce}';`;
47
    if (is_chrome)
48
	rule += `script-src-elem 'nonce-${nonce}';`;
50 49

  
51
	console.log("modified headers", url, headers);
50
    headers.push({
51
	name : "content-security-policy",
52
	value : rule
53
    });
52 54

  
53
	return {responseHeaders: headers};
54
    }
55
    return {responseHeaders: headers};
56
}
55 57

  
56
    async function start() {
57
	storage = await get_storage();
58
	query_best = await get_query_best();
58
async function start_policy_injector()
59
{
60
    storage = await get_storage();
61
    query_best = await get_query_best();
59 62

  
60
	let extra_opts = ["blocking", "responseHeaders"];
61
	if (is_chrome)
62
	    extra_opts.push("extraHeaders");
63
    let extra_opts = ["blocking", "responseHeaders"];
64
    if (is_chrome)
65
	extra_opts.push("extraHeaders");
63 66

  
64
	browser.webRequest.onHeadersReceived.addListener(
65
	    inject,
66
	    {
67
		urls: ["<all_urls>"],
68
		types: ["main_frame", "sub_frame"]
69
	    },
70
	    extra_opts
71
	);
72
    }
67
    browser.webRequest.onHeadersReceived.addListener(
68
	inject,
69
	{
70
	    urls: ["<all_urls>"],
71
	    types: ["main_frame", "sub_frame"]
72
	},
73
	extra_opts
74
    );
75
}
73 76

  
74
    window.start_policy_injector = start;
75
})();
77
/*
78
 * EXPORTS_START
79
 * EXPORT start_policy_injector
80
 * EXPORTS_END
81
 */
background/settings_query.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
9

  
10
(() => {
11
    const make_once = window.make_once;
12
    const get_storage = window.get_storage;
8
/*
9
 * IMPORTS_START
10
 * IMPORT make_once
11
 * IMPORT get_storage
12
 * IMPORT TYPE_PREFIX
13
 * IMPORTS_END
14
 */
13 15

  
14
    var storage;
16
var storage;
15 17

  
16
    var exports = {};
18
var exports = {};
17 19

  
18
    async function init(fun)
19
    {
20
	storage = await get_storage();
20
async function init(fun)
21
{
22
    storage = await get_storage();
21 23

  
22
	return fun;
23
    }
24
    return fun;
25
}
24 26

  
25
    // TODO: also support urls with specified ports as well as `data:' urls
26
    function query(url, multiple)
27
    {
28
	let proto_re = "[a-zA-Z]*:\/\/";
29
	let domain_re = "[^/?#]+";
30
	let segments_re = "/[^?#]*";
31
	let query_re = "\\?[^#]*";
27
// TODO: also support urls with specified ports
28
function query(url, multiple)
29
{
30
    let proto_re = "[a-zA-Z]*:\/\/";
31
    let domain_re = "[^/?#]+";
32
    let segments_re = "/[^?#]*";
33
    let query_re = "\\?[^#]*";
32 34

  
33
	let url_regex = new RegExp(`\
35
    let url_regex = new RegExp(`\
34 36
^\
35 37
(${proto_re})\
36 38
(${domain_re})\
......
39 41
#?.*\$\
40 42
`);
41 43

  
42
	let regex_match = url_regex.exec(url);
43
	if (regex_match === null) {
44
	    console.log("bad url format", url);
45
	    return multiple ? [] : [undefined, undefined];
46
	}
44
    let regex_match = url_regex.exec(url);
45
    if (regex_match === null) {
46
	console.log("bad url format", url);
47
	return multiple ? [] : [undefined, undefined];
48
    }
49

  
50
    let [_, proto, domain, segments, query] = regex_match;
51

  
52
    domain = domain.split(".");
53
    let segments_trailing_dash =
54
	segments && segments[segments.length - 1] === "/";
55
    segments = (segments || "").split("/").filter(s => s !== "");
56
    segments.unshift("");
57

  
58
    let matched = [];
59

  
60
    for (let d_slice = 0; d_slice < domain.length; d_slice++) {
61
	let domain_part = domain.slice(d_slice).join(".");
62
	let domain_wildcards = [];
63
	if (d_slice === 0)
64
	    domain_wildcards.push("");
65
	if (d_slice === 1)
66
	    domain_wildcards.push("*.");
67
	if (d_slice > 0)
68
	    domain_wildcards.push("**.");
69
	domain_wildcards.push("***.");
70

  
71
	for (let domain_wildcard of domain_wildcards) {
72
	    let domain_pattern = domain_wildcard + domain_part;
73

  
74
	    for (let s_slice = segments.length; s_slice > 0; s_slice--) {
75
		let segments_part = segments.slice(0, s_slice).join("/");
76
		let segments_wildcards = [];
77
		if (s_slice === segments.length) {
78
		    if (segments_trailing_dash)
79
			segments_wildcards.push("/");
80
		    segments_wildcards.push("");
81
		}
82
		if (s_slice === segments.length - 1) {
83
		    if (segments[s_slice] !== "*")
84
			segments_wildcards.push("/*");
85
		}
86
		if (s_slice < segments.length &&
87
		    (segments[s_slice] !== "**" ||
88
		     s_slice < segments.length - 1))
89
		    segments_wildcards.push("/**");
90
		if (segments[s_slice] !== "***" ||
91
		    s_slice < segments.length)
92
		    segments_wildcards.push("/***");
93

  
94
		for (let segments_wildcard of segments_wildcards) {
95
		    let segments_pattern =
96
			segments_part + segments_wildcard;
47 97

  
48
	let [_, proto, domain, segments, query] = regex_match;
49

  
50
	domain = domain.split(".");
51
	let segments_trailing_dash =
52
	    segments && segments[segments.length - 1] === "/";
53
	segments = (segments || "").split("/").filter(s => s !== "");
54
	segments.unshift("");
55

  
56
	let matched = [];
57

  
58
	for (let d_slice = 0; d_slice < domain.length; d_slice++) {
59
	    let domain_part = domain.slice(d_slice).join(".");
60
	    let domain_wildcards = [];
61
	    if (d_slice === 0)
62
		domain_wildcards.push("");
63
	    if (d_slice === 1)
64
		domain_wildcards.push("*.");
65
	    if (d_slice > 0)
66
		domain_wildcards.push("**.");
67
	    domain_wildcards.push("***.");
68

  
69
	    for (let domain_wildcard of domain_wildcards) {
70
		let domain_pattern = domain_wildcard + domain_part;
71

  
72
		for (let s_slice = segments.length; s_slice > 0; s_slice--) {
73
		    let segments_part = segments.slice(0, s_slice).join("/");
74
		    let segments_wildcards = [];
75
		    if (s_slice === segments.length) {
76
			if (segments_trailing_dash)
77
			    segments_wildcards.push("/");
78
			segments_wildcards.push("");
79
		    }
80
		    if (s_slice === segments.length - 1) {
81
			if (segments[s_slice] !== "*")
82
			    segments_wildcards.push("/*");
83
		    }
84
		    if (s_slice < segments.length &&
85
			(segments[s_slice] !== "**" ||
86
			 s_slice < segments.length - 1))
87
			segments_wildcards.push("/**");
88
		    if (segments[s_slice] !== "***" ||
89
			s_slice < segments.length)
90
			segments_wildcards.push("/***");
91

  
92
		    for (let segments_wildcard of segments_wildcards) {
93
			let segments_pattern =
94
			    segments_part + segments_wildcard;
95

  
96
			let pattern = proto + domain_pattern + segments_pattern;
97
			console.log("trying", pattern);
98
			let settings = storage.get(TYPE_PREFIX.PAGE, pattern);
99

  
100
			if (settings === undefined)
101
			    continue;
102

  
103
			if (!multiple)
104
			    return [pattern, settings];
105

  
106
			matched.push([pattern, settings]);
107
		    }
98
		    let pattern = proto + domain_pattern + segments_pattern;
99
		    console.log("trying", pattern);
100
		    let settings = storage.get(TYPE_PREFIX.PAGE, pattern);
101

  
102
		    if (settings === undefined)
103
			continue;
104

  
105
		    if (!multiple)
106
			return [pattern, settings];
107

  
108
		    matched.push([pattern, settings]);
108 109
		}
109 110
	    }
110 111
	}
111

  
112
	return multiple ?  matched : [undefined, undefined];
113 112
    }
114 113

  
115
    function query_best(url)
116
    {
117
	return query(url, false);
118
    }
114
    return multiple ?  matched : [undefined, undefined];
115
}
119 116

  
120
    function query_all(url)
121
    {
122
	return query(url, true);
123
    }
117
function query_best(url)
118
{
119
    return query(url, false);
120
}
121

  
122
function query_all(url)
123
{
124
    return query(url, true);
125
}
124 126

  
125
    window.get_query_best = make_once(() => init(query_best));
126
    window.get_query_all = make_once(() => init(query_all));
127
})();
127
const get_query_best = make_once(() => init(query_best));
128
const get_query_all = make_once(() => init(query_all));
129

  
130
/*
131
 * EXPORTS_START
132
 * EXPORT get_query_best
133
 * EXPORT get_query_all
134
 * EXPORTS_END
135
 */
background/storage.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
9

  
10
(() => {
11
    const TYPE_PREFIX = window.TYPE_PREFIX;
12
    const TYPE_NAME = window.TYPE_NAME;
13
    const list_prefixes = window.list_prefixes;
14
    const make_lock = window.make_lock;
15
    const lock = window.lock;
16
    const unlock = window.unlock;
17
    const make_once = window.make_once;
18
    const browser = window.browser;
19
    const is_chrome = window.is_chrome;
20

  
21
    var exports = {};
22

  
23
    /* We're yet to decide how to handle errors... */
24

  
25
    /* Here are some basic wrappers for storage API functions */
26

  
27
    async function get(key)
28
    {
29
	try {
30
	    /* Fix for fact that Chrome does not use promises here */
31
	    let promise = is_chrome ?
32
		new Promise((resolve, reject) =>
33
			    chrome.storage.local.get(key,
34
						     val => resolve(val))) :
35
		browser.storage.local.get(key);
36

  
37
	    return (await promise)[key];
38
	} catch (e) {
39
	    console.log(e);
40
	}
41
    }
8
/*
9
 * IMPORTS_START
10
 * IMPORT TYPE_PREFIX
11
 * IMPORT TYPE_NAME
12
 * IMPORT list_prefixes
13
 * IMPORT make_lock
14
 * IMPORT lock
15
 * IMPORT unlock
16
 * IMPORT make_once
17
 * IMPORT browser
18
 * IMPORT is_chrome
19
 * IMPORTS_END
20
 */
42 21

  
43
    async function set(key, value)
44
    {
45
	try {
46
	    return browser.storage.local.set({[key]: value});
47
	} catch (e) {
48
	    console.log(e);
49
	}
50
    }
22
var exports = {};
51 23

  
52
    async function setn(keys_and_values)
53
    {
54
	let obj = Object();
55
	while (keys_and_values.length > 1) {
56
	    let value = keys_and_values.pop();
57
	    let key = keys_and_values.pop();
58
	    obj[key] = value;
59
	}
24
/* We're yet to decide how to handle errors... */
60 25

  
61
	try {
62
	    return browser.storage.local.set(obj);
63
	} catch (e) {
64
	    console.log(e);
65
	}
66
    }
26
/* Here are some basic wrappers for storage API functions */
67 27

  
68
    async function set_var(name, value)
69
    {
70
	return set(TYPE_PREFIX.VAR + name, value);
71
    }
28
async function get(key)
29
{
30
    try {
31
	/* Fix for fact that Chrome does not use promises here */
32
	let promise = is_chrome ?
33
	    new Promise((resolve, reject) =>
34
			chrome.storage.local.get(key,
35
						 val => resolve(val))) :
36
	    browser.storage.local.get(key);
72 37

  
73
    async function get_var(name)
74
    {
75
	return get(TYPE_PREFIX.VAR + name);
38
	return (await promise)[key];
39
    } catch (e) {
40
	console.log(e);
41
    }
42
}
43

  
44
async function set(key, value)
45
{
46
    try {
47
	return browser.storage.local.set({[key]: value});
48
    } catch (e) {
49
	console.log(e);
50
    }
51
}
52

  
53
async function setn(keys_and_values)
54
{
55
    let obj = Object();
56
    while (keys_and_values.length > 1) {
57
	let value = keys_and_values.pop();
58
	let key = keys_and_values.pop();
59
	obj[key] = value;
76 60
    }
77 61

  
78
    /*
79
     * A special case of persisted variable is one that contains list
80
     * of items.
81
     */
82

  
83
    async function get_list_var(name)
84
    {
85
	let list = await get_var(name);
86

  
87
	return list === undefined ? [] : list;
62
    try {
63
	return browser.storage.local.set(obj);
64
    } catch (e) {
65
	console.log(e);
88 66
    }
67
}
89 68

  
90
    /* We maintain in-memory copies of some stored lists. */
69
async function set_var(name, value)
70
{
71
    return set(TYPE_PREFIX.VAR + name, value);
72
}
91 73

  
92
    async function list(prefix)
93
    {
94
	let name = TYPE_NAME[prefix] + "s"; /* Make plural. */
95
	let map = new Map();
74
async function get_var(name)
75
{
76
    return get(TYPE_PREFIX.VAR + name);
77
}
96 78

  
97
	for (let item of await get_list_var(name))
98
	    map.set(item, await get(prefix + item));
79
/*
80
 * A special case of persisted variable is one that contains list
81
 * of items.
82
 */
99 83

  
100
	return {map, prefix, name, listeners : new Set(), lock : make_lock()};
101
    }
84
async function get_list_var(name)
85
{
86
    let list = await get_var(name);
102 87

  
103
    var pages;
104
    var bags;
105
    var scripts;
88
    return list === undefined ? [] : list;
89
}
106 90

  
107
    var list_by_prefix = {};
91
/* We maintain in-memory copies of some stored lists. */
108 92

  
109
    async function init()
110
    {
111
	for (let prefix of list_prefixes)
112
	    list_by_prefix[prefix] = await list(prefix);
93
async function list(prefix)
94
{
95
    let name = TYPE_NAME[prefix] + "s"; /* Make plural. */
96
    let map = new Map();
113 97

  
114
	return exports;
115
    }
98
    for (let item of await get_list_var(name))
99
	map.set(item, await get(prefix + item));
116 100

  
117
    /*
118
     * Facilitate listening to changes
119
     */
101
    return {map, prefix, name, listeners : new Set(), lock : make_lock()};
102
}
120 103

  
121
    exports.add_change_listener = function (cb, prefixes=list_prefixes)
122
    {
123
	if (typeof(prefixes) === "string")
124
	    prefixes = [prefixes];
104
var pages;
105
var bags;
106
var scripts;
125 107

  
126
	for (let prefix of prefixes)
127
	    list_by_prefix[prefix].listeners.add(cb);
128
    }
108
var list_by_prefix = {};
129 109

  
130
    exports.remove_change_listener = function (cb, prefixes=list_prefixes)
131
    {
132
	if (typeof(prefixes) === "string")
133
	    prefixes = [prefixes];
110
async function init()
111
{
112
    for (let prefix of list_prefixes)
113
	list_by_prefix[prefix] = await list(prefix);
134 114

  
135
	for (let prefix of prefixes)
136
	    list_by_prefix[prefix].listeners.delete(cb);
137
    }
115
    return exports;
116
}
138 117

  
139
    function broadcast_change(change, list)
140
    {
141
	for (let listener_callback of list.listeners)
142
	    listener_callback(change);
143
    }
118
/*
119
 * Facilitate listening to changes
120
 */
144 121

  
145
    /* Prepare some hepler functions to get elements of a list */
122
exports.add_change_listener = function (cb, prefixes=list_prefixes)
123
{
124
    if (typeof(prefixes) === "string")
125
	prefixes = [prefixes];
146 126

  
147
    function list_items_it(list, with_values=false)
148
    {
149
	return with_values ? list.map.entries() : list.map.keys();
150
    }
127
    for (let prefix of prefixes)
128
	list_by_prefix[prefix].listeners.add(cb);
129
}
151 130

  
152
    function list_entries_it(list)
153
    {
154
	return list_items_it(list, true);
155
    }
131
exports.remove_change_listener = function (cb, prefixes=list_prefixes)
132
{
133
    if (typeof(prefixes) === "string")
134
	prefixes = [prefixes];
156 135

  
157
    function list_items(list, with_values=false)
158
    {
159
	let array = [];
136
    for (let prefix of prefixes)
137
	list_by_prefix[prefix].listeners.delete(cb);
138
}
160 139

  
161
	for (let item of list_items_it(list, with_values))
162
	    array.push(item);
140
function broadcast_change(change, list)
141
{
142
    for (let listener_callback of list.listeners)
143
	listener_callback(change);
144
}
163 145

  
164
	return array;
165
    }
146
/* Prepare some hepler functions to get elements of a list */
166 147

  
167
    function list_entries(list)
168
    {
169
	return list_items(list, true);
170
    }
148
function list_items_it(list, with_values=false)
149
{
150
    return with_values ? list.map.entries() : list.map.keys();
151
}
171 152

  
172
    /*
173
     * Below we make additional effort to update map of given kind of items
174
     * every time an item is added/removed to keep everything coherent.
175
     */
176
    async function set_item(item, value, list)
177
    {
178
	await lock(list.lock);
179
	let result = await _set_item(...arguments);
180
	unlock(list.lock)
181
	return result;
182
    }
183
    async function _set_item(item, value, list)
184
    {
185
	let key = list.prefix + item;
186
	let old_val = list.map.get(item);
187
	if (old_val === undefined) {
188
	    let items = list_items(list);
189
	    items.push(item);
190
	    await setn([key, value, "_" + list.name, items]);
191
	} else {
192
	    await set(key, value);
193
	}
153
function list_entries_it(list)
154
{
155
    return list_items_it(list, true);
156
}
194 157

  
195
	list.map.set(item, value)
158
function list_items(list, with_values=false)
159
{
160
    let array = [];
196 161

  
197
	let change = {
198
	    prefix : list.prefix,
199
	    item,
200
	    old_val,
201
	    new_val : value
202
	};
162
    for (let item of list_items_it(list, with_values))
163
	array.push(item);
203 164

  
204
	broadcast_change(change, list);
165
    return array;
166
}
205 167

  
206
	return old_val;
207
    }
168
function list_entries(list)
169
{
170
    return list_items(list, true);
171
}
208 172

  
209
    // TODO: The actual idea to set value to undefined is good - this way we can
210
    //       also set a new list of items in the same API call. But such key
211
    //       is still stored in the storage. We need to somehow remove it later.
212
    //       For that, we're going to have to store 1 more list of each kind.
213
    async function remove_item(item, list)
214
    {
215
	await lock(list.lock);
216
	let result = await _remove_item(...arguments);
217
	unlock(list.lock)
218
	return result;
173
/*
174
 * Below we make additional effort to update map of given kind of items
175
 * every time an item is added/removed to keep everything coherent.
176
 */
177
async function set_item(item, value, list)
178
{
179
    await lock(list.lock);
180
    let result = await _set_item(...arguments);
181
    unlock(list.lock)
182
    return result;
183
}
184
async function _set_item(item, value, list)
185
{
186
    let key = list.prefix + item;
187
    let old_val = list.map.get(item);
188
    if (old_val === undefined) {
189
	let items = list_items(list);
190
	items.push(item);
191
	await setn([key, value, "_" + list.name, items]);
192
    } else {
193
	await set(key, value);
219 194
    }
220
    async function _remove_item(item, list)
221
    {
222
	let old_val = list.map.get(item);
195

  
196
    list.map.set(item, value)
197

  
198
    let change = {
199
	prefix : list.prefix,
200
	item,
201
	old_val,
202
	new_val : value
203
    };
204

  
205
    broadcast_change(change, list);
206

  
207
    return old_val;
208
}
209

  
210
// TODO: The actual idea to set value to undefined is good - this way we can
211
//       also set a new list of items in the same API call. But such key
212
//       is still stored in the storage. We need to somehow remove it later.
213
//       For that, we're going to have to store 1 more list of each kind.
214
async function remove_item(item, list)
215
{
216
    await lock(list.lock);
217
    let result = await _remove_item(...arguments);
218
    unlock(list.lock)
219
    return result;
220
}
221
async function _remove_item(item, list)
222
{
223
    let old_val = list.map.get(item);
224
    if (old_val === undefined)
225
	return;
226

  
227
    let key = list.prefix + item;
228
    let items = list_items(list);
229
    let index = items.indexOf(item);
230
    items.splice(index, 1);
231

  
232
    await setn([key, undefined, "_" + list.name, items]);
233

  
234
    list.map.delete(item);
235

  
236
    let change = {
237
	prefix : list.prefix,
238
	item,
239
	old_val,
240
	new_val : undefined
241
    };
242

  
243
    broadcast_change(change, list);
244

  
245
    return old_val;
246
}
247

  
248
// TODO: same as above applies here
249
async function replace_item(old_item, new_item, list, new_val=undefined)
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff