Project

General

Profile

« Previous | Next » 

Revision 6247f163

Added by koszko about 2 years ago

enable toggling of global script blocking policy\n\nThis commit also introduces `light_storage' module which is later going to replace the storage code we use right now.\nAlso included is a hack to properly display scrollbars under Mozilla (needs testing on newer Mozilla browsers).

View differences:

background/main.js
9 9
 * IMPORTS_START
10 10
 * IMPORT TYPE_PREFIX
11 11
 * IMPORT get_storage
12
 * IMPORT light_storage
12 13
 * IMPORT start_storage_server
13 14
 * IMPORT start_page_actions_server
14 15
 * IMPORT browser
......
50 51

  
51 52

  
52 53
let storage;
54
let policy_observable = {};
53 55

  
54 56
function on_headers_received(details)
55 57
{
......
58 60
	return;
59 61

  
60 62
    const [pattern, settings] = query_best(storage, details.url);
61
    const allow = !!(settings && settings.allow);
63
    const allow = !!(settings ? settings.allow : policy_observable.value);
62 64
    const nonce = gen_nonce();
63 65
    const policy = {allow, url, nonce};
64 66

  
......
114 116
	{urls: ["<all_urls>"], types: all_types},
115 117
	extra_opts.concat("requestHeaders")
116 118
    );
119

  
120
    policy_observable = await light_storage.observe_var("default_allow");
117 121
}
118 122

  
119 123
start_webRequest_operations();
background/page_actions_server.js
8 8
/*
9 9
 * IMPORTS_START
10 10
 * IMPORT get_storage
11
 * IMPORT light_storage
11 12
 * IMPORT TYPE_PREFIX
12 13
 * IMPORT CONNECTION_TYPE
13 14
 * IMPORT browser
......
20 21

  
21 22
var storage;
22 23
var handler;
24
let policy_observable;
23 25

  
24 26
function send_actions(url, port)
25 27
{
26
    const [pattern, settings] = query_best(storage, url);
28
    let [pattern, settings] = query_best(storage, url);
29
    if (!settings)
30
	settings = {allow: policy_observable && policy_observable.value};
27 31
    const repos = storage.get_all(TYPE_PREFIX.REPO);
28 32

  
29 33
    port.postMessage(["settings", [pattern, settings, repos]]);
30 34

  
31
    if (settings === undefined)
32
	return;
33

  
34 35
    let components = settings.components;
35 36
    let processed_bags = new Set();
36 37

  
......
127 128
    storage = await get_storage();
128 129

  
129 130
    listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
131

  
132
    policy_observable = await light_storage.observe_var("default_allow");
130 133
}
131 134

  
132 135
/*
build.sh
291 291
    cp html/*.css $BUILDDIR/html
292 292
    mkdir $BUILDDIR/icons
293 293
    cp icons/*.png $BUILDDIR/icons
294

  
295
    if [ "$BROWSER" = "chromium" ]; then
296
	for MOZILLA_FILE in $(find $BUILDDIR -name "MOZILLA_*"); do
297
	    echo > "$MOZILLA_FILE"
298
	done
299
    fi
300
    if [ "$BROWSER" = "mozilla" ]; then
301
	for CHROMIUM_FILE in $(find $BUILDDIR -name "CHROMIUM_*"); do
302
	    echo > "$CHROMIUM_FILE"
303
	done
304
    fi
294 305
}
295 306

  
296 307
main "$@"
common/observable.js
6 6
 * Redistribution terms are gathered in the `copyright' file.
7 7
 */
8 8

  
9
function make()
10
{
11
    return new Set();
12
}
9
const make = (value=undefined) => ({value, listeners: new Set()});
10
const subscribe = (observable, cb) => observable.listeners.add(cb);
11
const unsubscribe = (observable, cb) => observable.listeners.delete(cb);
13 12

  
14
function subscribe(observable, cb)
15
{
16
    observable.add(cb);
17
}
18

  
19
function unsubscribe(observable, cb)
20
{
21
    observable.delete(cb);
22
}
13
const silent_set = (observable, value) => observable.value = value;
14
const broadcast = (observable, ...values) =>
15
      observable.listeners.forEach(cb => cb(...values));
23 16

  
24
function broadcast(observable, event)
17
function set(observable, value)
25 18
{
26
    for (const callback of observable)
27
	callback(event);
19
    const old_value = observable.value;
20
    silent_set(observable, value);
21
    broadcast(observable, value, old_value);
28 22
}
29 23

  
30
const observables = {make, subscribe, unsubscribe, broadcast};
24
const observables = {make, subscribe, unsubscribe, broadcast, silent_set, set};
31 25

  
32 26
/*
33 27
 * EXPORTS_START
common/storage_light.js
1
/**
2
 * part of Hachette
3
 * Storage manager, lighter than the previous one.
4
 *
5
 * Copyright (C) 2021 Wojtek Kosior
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

  
9
/*
10
 * IMPORTS_START
11
 * IMPORT TYPE_PREFIX
12
 * IMPORT raw_storage
13
 * IMPORT is_mozilla
14
 * IMPORT observables
15
 */
16

  
17
const reg_spec = new Set(["\\", "[", "]", "(", ")", "{", "}", ".", "*", "+"]);
18
const escape_reg_special = c => reg_spec.has(c) ? "\\" + c : c;
19

  
20
function make_regex(name)
21
{
22
    return new RegExp(`^${name.split("").map(escape_reg_special).join("")}\$`);
23
}
24

  
25
const listeners_by_callback = new Map();
26

  
27
function listen(callback, prefix, name)
28
{
29
    let by_prefix = listeners_by_callback.get(callback);
30
    if (!by_prefix) {
31
	by_prefix = new Map();
32
	listeners_by_callback.set(callback, by_prefix);
33
    }
34

  
35
    let by_name = by_prefix.get(prefix);
36
    if (!by_name) {
37
	by_name = new Map();
38
	by_prefix.set(prefix, by_name);
39
    }
40

  
41
    let name_reg = by_name.get(name);
42
    if (!name_reg) {
43
	name_reg = name.test ? name : make_regex(name);
44
	by_name.set(name, name_reg);
45
    }
46
}
47

  
48
function no_listen(callback, prefix, name)
49
{
50
    const by_prefix = listeners_by_callback.get(callback);
51
    if (!by_prefix)
52
	return;
53

  
54
    const by_name = by_prefix.get(prefix);
55
    if (!by_name)
56
	return;
57

  
58
    const name_reg = by_name.get(name);
59
    if (!name_reg)
60
	return;
61

  
62
    by_name.delete(name);
63

  
64
    if (by_name.size === 0)
65
	by_prefix.delete(prefix);
66

  
67
    if (by_prefix.size === 0)
68
	listeners_by_callback.delete(callback);
69
}
70

  
71
function storage_change_callback(changes, area)
72
{
73
    if (is_mozilla && area !== "local")
74
    {console.log("area", area);return;}
75

  
76
    for (const item of Object.keys(changes)) {
77
	for (const [callback, by_prefix] of listeners_by_callback.entries()) {
78
	    const by_name = by_prefix.get(item[0]);
79
	    if (!by_name)
80
		continue;
81

  
82
	    for (const reg of by_name.values()) {
83
		if (!reg.test(item.substring(1)))
84
		    continue;
85

  
86
		try {
87
		    callback(item, changes[item]);
88
		} catch(e) {
89
		    console.error(e);
90
		}
91
	    }
92
	}
93
    }
94
}
95

  
96
raw_storage.listen(storage_change_callback);
97

  
98

  
99
const created_observables = new Map();
100

  
101
async function observe(prefix, name)
102
{
103
    const observable = observables.make();
104
    const callback = (it, ch) => observables.set(observable, ch.newValue);
105
    listen(callback, prefix, name);
106
    created_observables.set(observable, [callback, prefix, name]);
107
    observables.silent_set(observable, await raw_storage.get(prefix + name));
108

  
109
    return observable;
110
}
111

  
112
const observe_var = name => observe(TYPE_PREFIX.VAR, name);
113

  
114
function no_observe(observable)
115
{
116
    no_listen(...created_observables.get(observable) || []);
117
    created_observables.delete(observable);
118
}
119

  
120
const light_storage = {};
121
Object.assign(light_storage, raw_storage);
122
Object.assign(light_storage,
123
	      {listen, no_listen, observe, observe_var, no_observe});
124

  
125
/*
126
 * EXPORTS_START
127
 * EXPORT light_storage
128
 * EXPORTS_END
129
 */
common/storage_raw.js
26 26

  
27 27
async function set(key_or_object, value)
28 28
{
29
    return browser.storage.local.set(typeof key_or_object === "object" ?
30
				     key_or_object : {[key]: value});
29
    const arg = typeof key_or_object === "object" ?
30
	  key_or_object : {[key_or_object]: value};
31
    return browser.storage.local.set(arg);
31 32
}
32 33

  
33 34
async function set_var(name, value)
......
40 41
    return get(TYPE_PREFIX.VAR + name);
41 42
}
42 43

  
43
const raw_storage = {get, set, get_var, set_var};
44
const on_changed = browser.storage.onChanged || browser.storage.local.onChanged;
45
const listen = cb => on_changed.addListener(cb);
46
const no_listen = cb => on_changed.removeListener(cb);
47

  
48
const raw_storage = {get, set, get_var, set_var, listen, no_listen};
44 49

  
45 50
/*
46 51
 * EXPORTS_START
content/main.js
123 123
    }
124 124

  
125 125
    if (!policy) {
126
	console.warn("Using default policy!");
126
	console.warn("Using fallback policy!");
127 127
	policy = {allow: false, nonce: gen_nonce()};
128 128
    }
129 129

  
content/page_actions.js
36 36
    }
37 37
    if (action === "settings") {
38 38
	report_settings(data);
39
	policy_received_callback({url, allow: !!data[1] && data[1].allow});
39
	policy_received_callback({url, allow: data[1].allow});
40 40
    }
41 41
}
42 42

  
html/MOZILLA_scrollbar_fix.css
1
/**
2
 * Hachette
3
 * Hacky fix for vertical scrollbar width being included in child's width.
4
 *
5
 * Copyright (C) 2021 Wojtek Kosior
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

  
9
/*
10
 * Under Mozilla browsers to avoid vertical scrollbar forcing horizontal
11
 * scrollbar to appear in an element we add the `firefox_scrollbars_hacky_fix'
12
 * class to an element for which width has to be reserved.
13
 *
14
 * This is a bit hacky and relies on some assumed width of Firefox scrollbar, I
15
 * know. And must be excluded from Chromium builds.
16
 *
17
 * I came up with this hack when working on popup. Before that I had the
18
 * scrollbar issue with tables in the options page and gave up there and made
19
 * the scrollbal always visible. Now we could try applying this "fix" there,
20
 * too!
21
 */
22

  
23
.firefox_scrollbars_hacky_fix {
24
    font-size: 0;
25
}
26

  
27
.firefox_scrollbars_hacky_fix>div {
28
    display: inline-block;
29
    width: -moz-available;
30
}
31

  
32
.firefox_scrollbars_hacky_fix>*>* {
33
    font-size: initial;
34
}
35

  
36
.firefox_scrollbars_hacky_fix::after {
37
    content: "";
38
    display: inline-block;
39
    visibility: hidden;
40
    font-size: initial;
41
    width: 14px;
42
}
43

  
44
.firefox_scrollbars_hacky_fix.has_inline_content::after {
45
    width: calc(14px - 0.3em);
46
}
html/base.css
100 100
    background: linear-gradient(#555, transparent);
101 101
}
102 102

  
103
.has_bottom_thin_line {
104
    border-bottom: dashed #4CAF50 1px;
105
}
106

  
107
.has_upper_thin_line {
108
    border-top: dashed #4CAF50 1px;
109
}
110

  
103 111
.nowrap {
104 112
    white-space: nowrap;
105 113
}
html/default_blocking_policy.html
1
<!--
2
    Copyright (C) 2021 Wojtek Kosior
3
    Redistribution terms are gathered in the `copyright' file.
4

  
5
    This is not a standalone page. This file is meant to be imported into other
6
    HTML code.
7
  -->
8
<style>
9
  #blocking_policy_div {
10
      line-height: 2em;
11
  }
12
</style>
13
<span id="blocking_policy_span">
14
  Default policy for unmatched pages is to
15
  <span id="current_policy_span" class="bold"></span>
16
  their own scripts.
17
  <button id="toggle_policy_but">Toggle policy</button>
18
</span>
html/default_blocking_policy.js
1
/**
2
 * part of Hachette
3
 * Default policy dialog logic.
4
 *
5
 * Copyright (C) 2021 Wojtek Kosior
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

  
9
/*
10
 * IMPORTS_START
11
 * IMPORT by_id
12
 * IMPORT light_storage
13
 * IMPORT observables
14
 * IMPORTS_END
15
 */
16

  
17
/*
18
 * Used with `default_blocking_policy.html' to allow user to choose whether to
19
 * block scripts globally or not.
20
 */
21

  
22
const blocking_policy_span = by_id("blocking_policy_span");
23
const current_policy_span = by_id("current_policy_span");
24
const toggle_policy_but = by_id("toggle_policy_but");
25

  
26
let policy_observable;
27

  
28
const update_policy =
29
      allowed => current_policy_span.textContent = allowed ? "allow" : "block";
30
const toggle_policy =
31
      () => light_storage.set_var("default_allow", !policy_observable.value);
32

  
33
async function init_default_policy_dialog()
34
{
35
    policy_observable = await light_storage.observe_var("default_allow");
36
    update_policy(policy_observable.value);
37
    observables.subscribe(policy_observable, update_policy);
38

  
39
    toggle_policy_but.addEventListener("click", toggle_policy);
40
    blocking_policy_span.classList.remove("hide");
41
}
42

  
43
/*
44
 * EXPORTS_START
45
 * EXPORT init_default_policy_dialog
46
 * EXPORTS_END
47
 */
html/display-panel.html
11 11
    <link type="text/css" rel="stylesheet" href="base.css" />
12 12
    <link type="text/css" rel="stylesheet" href="back_button.css" />
13 13
    <link type="text/css" rel="stylesheet" href="table.css" />
14
    <link type="text/css" rel="stylesheet" href="MOZILLA_scrollbar_fix.css" />
14 15
    <style>
15 16
      body {
16 17
	  width: max-content;
17
	  width: -moz-max-content;
18
	  width: -moz-fit-content;
18 19
      }
19 20

  
20 21
      .top>h2 {
......
114 115
      pre {
115 116
	  font-family: monospace;
116 117
	  background-color: white;
117
	  border-top: dashed #4CAF50 1px;
118
	  border-bottom: dashed #4CAF50 1px;
119 118
	  padding: 1px 5px;
120 119
      }
121 120

  
......
133 132
	  padding-right: 5px;
134 133
      }
135 134

  
135
      .padding_top {
136
	  padding-top: 5px;
137
      }
138

  
136 139
      .header {
137
	  border-bottom: dashed #4CAF50 1px;
138 140
	  padding-bottom: 0.3em;
139 141
	  margin-bottom: 0.5em;
140 142
	  text-align: center;
......
146 148
      }
147 149

  
148 150
      .footer {
149
	  border-top: dashed #4CAF50 1px;
150 151
	  padding-top: 0.3em;
151 152
	  margin-top: 0.5em;
152 153
	  text-align: center;
......
199 200
	  <label data-template="lbl">
200 201
	    <h3><div class="unroll_triangle"></div> script</h3>
201 202
	  </label>
202
	  <pre data-template="script_contents"></pre>
203
	  <pre class="has_bottom_thin_line has_upper_thin_line" data-template="script_contents"></pre>
203 204
	</div>
204 205
      </div>
205 206

  
......
242 243
	  Patterns higher are more specific and override the ones below.
243 244
	</aside>
244 245
      </div>
245
      <div class="table_wrapper">
246
      <div class="table_wrapper firefox_scrollbars_hacky_fix">
246 247
	<div>
247 248
	  <table>
248 249
	    <tbody id="possible_patterns">
......
250 251
	  </table>
251 252
	</div>
252 253
      </div>
254
      <div class="padding_inline padding_top has_upper_thin_line firefox_scrollbars_hacky_fix has_inline_content">
255
	<span class="nowrap">
256
	  <IMPORT html/default_blocking_policy.html />
257
	</span>
258
      </div>
253 259
    </div>
254 260

  
255 261
    <input id="show_queried_view_radio" type="radio" class="show_next" name="current_view"></input>
......
265 271
      <h3 id="privileged_notice" class="middle hide">Privileged page</h3>
266 272

  
267 273
      <div id="page_state" class="hide">
268
	<div class="header padding_inline">
274
	<div class="header padding_inline has_bottom_thin_line">
269 275
	  <label for="show_patterns_view_radio" class="button">
270 276
	    Edit settings for this page
271 277
	  </label>
......
317 323
	</div>
318 324
      </div>
319 325

  
320
      <div class="footer padding_inline">
326
      <div class="footer padding_inline has_upper_thin_line">
321 327
	<button id="settings_but" type="button">
322 328
	  Open Hachette settings
323 329
	</button>
html/display-panel.js
14 14
 *** temporarily, before all storage access gets reworked.
15 15
 * IMPORT get_remote_storage
16 16
 * IMPORT get_import_frame
17
 * IMPORT init_default_policy_dialog
17 18
 * IMPORT query_all
18 19
 * IMPORT CONNECTION_TYPE
19 20
 * IMPORT is_privileged_url
......
243 244
    if (type === "settings") {
244 245
	let [pattern, settings] = data;
245 246

  
246
	settings = settings || {};
247 247
	blocked_span.textContent = settings.allow ? "no" : "yes";
248 248

  
249 249
	if (pattern) {
......
254 254
	    view_pattern_but.addEventListener("click", settings_opener);
255 255
	} else {
256 256
	    pattern_span.textContent = "none";
257
	    blocked_span.textContent = blocked_span.textContent + " (default)";
257 258
	}
258 259

  
259 260
	const components = settings.components;
......
549 550

  
550 551
async function main()
551 552
{
553
    init_default_policy_dialog();
554

  
552 555
    storage = await get_remote_storage();
553 556
    import_frame = await get_import_frame();
554 557
    import_frame.onclose = () => show_queried_view_radio.checked = true;
html/import_frame.html
1
<!--
2
    Copyright (C) 2021 Wojtek Kosior
3
    Redistribution terms are gathered in the `copyright' file.
4

  
5
    This is not a standalone page. This file is meant to be imported into other
6
    HTML code.
7
  -->
1 8
<style>
2 9
  .padding_right {
3 10
      padding-right: 0.3em;
html/options.html
248 248
	</div>
249 249
      </div>
250 250
      <button id="add_page_but" type="button"> Add page </button>
251
      <IMPORT html/default_blocking_policy.html />
251 252
    </div>
252 253
    <div id="bags" class="tab">
253 254
      <div class="table_wrapper tight_table has_bottom_line has_upper_line">
html/options_main.js
17 17
 * IMPORT by_id
18 18
 * IMPORT matchers
19 19
 * IMPORT get_import_frame
20
 * IMPORT init_default_policy_dialog
20 21
 * IMPORTS_END
21 22
 */
22 23

  
......
670 671

  
671 672
async function main()
672 673
{
674
    init_default_policy_dialog();
675

  
673 676
    storage = await get_remote_storage();
674 677

  
675 678
    for (let prefix of list_prefixes) {

Also available in: Unified diff