Project

General

Profile

« Previous | Next » 

Revision b7e2870f

Added by koszko about 2 years ago

show some settings of the current page in the popup

View differences:

background/main.js
12 12
 * IMPORT start_storage_server
13 13
 * IMPORT start_page_actions_server
14 14
 * IMPORT start_policy_injector
15
 * IMPORT start_page_info_server
15 16
 * IMPORT browser
16 17
 * IMPORTS_END
17 18
 */
......
19 20
start_storage_server();
20 21
start_page_actions_server();
21 22
start_policy_injector();
23
start_page_info_server();
22 24

  
23 25
async function init_myext(install_details)
24 26
{
background/page_actions_server.js
21 21
var query_best;
22 22
var handler;
23 23

  
24
function send_scripts(url, port)
24
function send_actions(url, port)
25 25
{
26 26
    let [pattern, settings] = query_best(url);
27

  
28
    port.postMessage(["settings", [pattern, settings]]);
29

  
27 30
    if (settings === undefined)
28 31
	return;
29 32

  
......
31 34
    let processed_bags = new Set();
32 35

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

  
37 40
// TODO: parallelize script fetching
38
async function send_scripts_rec(components, port, processed_bags)
41
async function send_scripts(components, port, processed_bags)
39 42
{
40 43
    for (let [prefix, name] of components) {
41 44
	if (prefix === TYPE_PREFIX.BAG) {
......
52 55
	    }
53 56

  
54 57
	    processed_bags.add(name);
55
	    await send_scripts_rec(bag, port, processed_bags);
58
	    await send_scripts(bag, port, processed_bags);
59

  
56 60
	    processed_bags.delete(name);
57 61
	} else {
58 62
	    let script_text = await get_script_text(name);
59 63
	    if (script_text === undefined)
60 64
		continue;
61 65

  
62
	    port.postMessage({inject : [script_text]});
66
	    port.postMessage(["inject", [script_text]]);
63 67
	}
64 68
    }
65 69
}
......
127 131
    port.onMessage.removeListener(handler[0]);
128 132
    let url = message.url;
129 133
    console.log({url});
130
    send_scripts(url, port);
134
    send_actions(url, port);
131 135
}
132 136

  
133 137
function new_connection(port)
background/page_info_server.js
1
/**
2
 * part of Hachette
3
 * Serving of storage data corresponding to requested urls (server side).
4
 *
5
 * Copyright (C) 2021 Wojtek Kosior
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

  
9
/*
10
 * IMPORTS_START
11
 * IMPORT listen_for_connection
12
 * IMPORT get_storage
13
 * IMPORT get_query_all
14
 * IMPORT TYPE_PREFIX
15
 * IMPORT CONNECTION_TYPE
16
 * IMPORT url_matches
17
 * IMPORTS_END
18
 */
19

  
20
var storage;
21
var query_all;
22

  
23
function handle_change(connection_data, change)
24
{
25
    if (change.prefix !== TYPE_PREFIX.PAGE)
26
	return;
27

  
28
    connection_data.port.postMessage(["change", change]);
29
}
30

  
31
async function handle_subscription(connection_data, message)
32
{
33
    const [action, url] = message;
34
    if (action === "unsubscribe") {
35
	connection_data.subscribed.delete(url);
36
	return;
37
    }
38

  
39
    connection_data.subscribed.add(url);
40
    connection_data.port.postMessage(["new_url", query_all(url)]);
41
}
42

  
43
function remove_storage_listener(cb)
44
{
45
    storage.remove_change_listener(cb);
46
}
47

  
48
function new_connection(port)
49
{
50
    console.log("new page info connection!");
51

  
52
    const connection_data = {
53
	subscribed : new Set(),
54
	port
55
    };
56

  
57
    let _handle_change = change => handle_change(connection_data, change);
58

  
59
    storage.add_change_listener(_handle_change);
60

  
61
    port.onMessage.addListener(m => handle_subscription(connection_data, m));
62
    port.onDisconnect.addListener(() => remove_storage_listener(handle_change));
63
}
64

  
65
async function start_page_info_server()
66
{
67
    storage = await get_storage();
68
    query_all = await get_query_all();
69

  
70
    listen_for_connection(CONNECTION_TYPE.PAGE_INFO, new_connection);
71
}
72

  
73
/*
74
 * EXPORTS_START
75
 * EXPORT start_page_info_server
76
 * EXPORTS_END
77
 */
background/settings_query.js
10 10
 * IMPORT make_once
11 11
 * IMPORT get_storage
12 12
 * IMPORT TYPE_PREFIX
13
 * IMPORT for_each_possible_pattern
13 14
 * IMPORTS_END
14 15
 */
15 16

  
16 17
var storage;
17 18

  
18
var exports = {};
19

  
20 19
async function init(fun)
21 20
{
22 21
    storage = await get_storage();
......
24 23
    return fun;
25 24
}
26 25

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

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

  
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 = [];
28
    const settings = storage.get(TYPE_PREFIX.PAGE, pattern);
59 29

  
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("***.");
30
    if (settings === undefined)
31
	return;
70 32

  
71
	for (let domain_wildcard of domain_wildcards) {
72
	    let domain_pattern = domain_wildcard + domain_part;
33
    matched.push([pattern, settings]);
73 34

  
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;
97

  
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];
35
    if (!multiple)
36
	return false;
37
}
107 38

  
108
		    matched.push([pattern, settings]);
109
		}
110
	    }
111
	}
112
    }
39
function query(url, multiple)
40
{
41
    const matched = [];
42
    for_each_possible_pattern(url, p => check_pattern(p, multiple, matched));
113 43

  
114
    return multiple ?  matched : [undefined, undefined];
44
    return multiple ? matched : (matched[0] || [undefined, undefined]);
115 45
}
116 46

  
117 47
function query_best(url)
common/connection_types.js
12 12

  
13 13
const CONNECTION_TYPE = {
14 14
    REMOTE_STORAGE : "0",
15
    PAGE_ACTIONS : "1"
15
    PAGE_ACTIONS : "1",
16
    PAGE_INFO : "2",
17
    ACTIVITY_INFO : "3"
16 18
};
17 19

  
18 20
/*
common/misc.js
10 10
 * IMPORT sha256
11 11
 * IMPORT browser
12 12
 * IMPORT is_chrome
13
 * IMPORT TYPE_NAME
13 14
 * IMPORTS_END
14 15
 */
15 16

  
......
71 72
    return rule;
72 73
}
73 74

  
75
/*
76
 * Print item together with type, e.g.
77
 * nice_name("s", "hello") → "hello (script)"
78
 */
79
function nice_name(prefix, name)
80
{
81
    return `${name} (${TYPE_NAME[prefix]})`;
82
}
83

  
84
/* Open settings tab with given item's editing already on. */
85
function open_in_settings(prefix, name)
86
{
87
    name = encodeURIComponent(name);
88
    const url = browser.runtime.getURL("html/options.html#" + prefix + name);
89
    window.open(url, "_blank");
90
}
91

  
92
/* Check if url corresponds to a browser's special page */
93
function is_privileged_url(url)
94
{
95
    return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url);
96
}
97

  
74 98
/*
75 99
 * EXPORTS_START
76 100
 * EXPORT gen_unique
77 101
 * EXPORT url_item
78 102
 * EXPORT url_extract_target
79 103
 * EXPORT csp_rule
104
 * EXPORT nice_name
105
 * EXPORT open_in_settings
106
 * EXPORT is_privileged_url
80 107
 * EXPORTS_END
81 108
 */
common/patterns.js
1
/**
2
 * Hydrilla/Lernette operations on page url patterns
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 * Redistribution terms are gathered in the `copyright' file.
6
 */
7

  
8
const proto_re = "[a-zA-Z]*:\/\/";
9
const domain_re = "[^/?#]+";
10
const segments_re = "/[^?#]*";
11
const query_re = "\\?[^#]*";
12

  
13
const url_regex = new RegExp(`\
14
^\
15
(${proto_re})\
16
(${domain_re})\
17
(${segments_re})?\
18
(${query_re})?\
19
#?.*\$\
20
`);
21

  
22
function deconstruct_url(url)
23
{
24
    const regex_match = url_regex.exec(url);
25
    if (regex_match === null)
26
	return undefined;
27

  
28
    let [_, proto, domain, path, query] = regex_match;
29

  
30
    domain = domain.split(".");
31
    let path_trailing_dash =
32
	path && path[path.length - 1] === "/";
33
    path = (path || "").split("/").filter(s => s !== "");
34
    path.unshift("");
35

  
36
    return {proto, domain, path, query, path_trailing_dash};
37
}
38

  
39
/* Be sane: both arguments should be arrays of length >= 2 */
40
function domain_matches(url_domain, pattern_domain)
41
{
42
    const length_difference = url_domain.length - pattern_domain.length;
43

  
44
    for (let i = 1; i <= url_domain.length; i++) {
45
	const url_part = url_domain[url_domain.length - i];
46
	const pattern_part = pattern_domain[pattern_domain.length - i];
47

  
48
	if (pattern_domain.length === i) {
49
	    if (pattern_part === "*")
50
		return length_difference === 0;
51
	    if (pattern_part === "**")
52
		return length_difference > 0;
53
	    if (pattern_part === "***")
54
		return true;
55
	    return length_difference === 0 && pattern_part === url_part;
56
	}
57

  
58
	if (pattern_part !== url_part)
59
	    return false;
60
    }
61

  
62
    return pattern_domain.length === url_domain.length + 1 &&
63
	pattern_domain[0] === "***";
64
}
65

  
66
function path_matches(url_path, url_trailing_dash,
67
		      pattern_path, pattern_trailing_dash)
68
{
69
    const dashes_ok = !(pattern_trailing_dash && !url_trailing_dash);
70

  
71
    if (pattern_path.length === 0)
72
	return url_path.length === 0 && dashes_ok;
73

  
74
    const length_difference = url_path.length - pattern_path.length;
75

  
76
    for (let i = 0; i < url_path.length; i++) {
77
	if (pattern_path.length === i + 1) {
78
	    if (pattern_path[i] === "*")
79
		return length_difference === 0;
80
	    if (pattern_path[i] === "**") {
81
		return length_difference > 0 ||
82
		    (url_path[i] === "**" && dashes_ok);
83
	    }
84
	    if (pattern_path[i] === "***")
85
		return length_difference >= 0;
86
	    return length_difference === 0 &&
87
		pattern_path[i] === url_path[i] && dashes_ok;
88
	}
89

  
90
	if (pattern_path[i] !== url_path[i])
91
	    return false;
92
    }
93

  
94
    return false;
95
}
96

  
97
function url_matches(url, pattern)
98
{
99
    const url_deco = deconstruct_url(url);
100
    const pattern_deco = deconstruct_url(pattern);
101

  
102
    if (url_deco === undefined || pattern_deco === undefined) {
103
	console.log(`bad comparison: ${url} and ${pattern}`);
104
	return false
105
    }
106

  
107
    if (pattern_deco.proto !== url_deco.proto)
108
	return false;
109

  
110
    return domain_matches(url_deco.domain, pattern_deco.domain) &&
111
	path_matches(url_deco.path, url_deco.path_trailing_dash,
112
		     pattern_deco.path, pattern_deco.path_trailing_dash);
113
}
114

  
115
/*
116
 * Call callback for every possible pattern that matches url. Return when there
117
 * are no more patterns or callback returns false.
118
 */
119
function for_each_possible_pattern(url, callback)
120
{
121
    const deco = deconstruct_url(url);
122

  
123
    if (deco === undefined) {
124
	console.log("bad url format", url);
125
	return;
126
    }
127

  
128
    for (let d_slice = 0; d_slice < deco.domain.length; d_slice++) {
129
	const domain_part = deco.domain.slice(d_slice).join(".");
130
	const domain_wildcards = [];
131
	if (d_slice === 0)
132
	    domain_wildcards.push("");
133
	if (d_slice === 1)
134
	    domain_wildcards.push("*.");
135
	if (d_slice > 0)
136
	    domain_wildcards.push("**.");
137
	domain_wildcards.push("***.");
138

  
139
	for (const domain_wildcard of domain_wildcards) {
140
	    const domain_pattern = domain_wildcard + domain_part;
141

  
142
	    for (let s_slice = deco.path.length; s_slice > 0; s_slice--) {
143
		const path_part = deco.path.slice(0, s_slice).join("/");
144
		const path_wildcards = [];
145
		if (s_slice === deco.path.length) {
146
		    if (deco.path_trailing_dash)
147
			path_wildcards.push("/");
148
		    path_wildcards.push("");
149
		}
150
		if (s_slice === deco.path.length - 1 &&
151
		    deco.path[s_slice] !== "*")
152
		    path_wildcards.push("/*");
153
		if (s_slice < deco.path.length &&
154
		    (deco.path[s_slice] !== "**" ||
155
		     s_slice < deco.path.length - 1))
156
		    path_wildcards.push("/**");
157
		if (deco.path[s_slice] !== "***" || s_slice < deco.path.length)
158
		    path_wildcards.push("/***");
159

  
160
		for (const path_wildcard of path_wildcards) {
161
		    const path_pattern = path_part + path_wildcard;
162

  
163
		    const pattern = deco.proto + domain_pattern + path_pattern;
164

  
165
		    if (callback(pattern) === false)
166
			return;
167
		}
168
	    }
169
	}
170
    }
171
}
172

  
173
function possible_patterns(url)
174
{
175
    const patterns = [];
176
    for_each_possible_pattern(url, patterns.push);
177

  
178
    return patterns;
179
}
180

  
181
/*
182
 * EXPORTS_START
183
 * EXPORT url_matches
184
 * EXPORT for_each_possible_pattern
185
 * EXPORT possible_patterns
186
 * EXPORTS_END
187
 */
content/activity_info_server.js
1
/**
2
 * part of Hachette
3
 * Informing about activities performed by content script (script injection,
4
 * script blocking).
5
 *
6
 * Copyright (C) 2021 Wojtek Kosior
7
 * Redistribution terms are gathered in the `copyright' file.
8
 */
9

  
10
/*
11
 * IMPORTS_START
12
 * IMPORT listen_for_connection
13
 * IMPORT CONNECTION_TYPE
14
 * IMPORTS_END
15
 */
16

  
17
var activities = [];
18
var ports = new Set();
19

  
20
function report_activity(name, data)
21
{
22
    const activity = [name, data];
23
    activities.push(activity);
24

  
25
    for (const port of ports)
26
	port.postMessage(activity);
27
}
28

  
29
function report_script(script_data)
30
{
31
    report_activity("script", script_data);
32
}
33

  
34
function report_settings(settings)
35
{
36
    report_activity("settings", settings);
37
}
38

  
39
function new_connection(port)
40
{
41
    console.log("new activity info connection!");
42

  
43
    ports.add(port);
44

  
45
    for (const activity of activities)
46
	port.postMessage(activity);
47
}
48

  
49
function start_activity_info_server()
50
{
51
    listen_for_connection(CONNECTION_TYPE.ACTIVITY_INFO, new_connection);
52
}
53

  
54
/*
55
 * EXPORTS_START
56
 * EXPORT start_activity_info_server
57
 * EXPORT report_script
58
 * EXPORT report_settings
59
 * EXPORTS_END
60
 */
content/main.js
12 12
 * IMPORT url_extract_target
13 13
 * IMPORT gen_unique
14 14
 * IMPORT csp_rule
15
 * IMPORT is_privileged_url
15 16
 * IMPORT sanitize_attributes
16 17
 * IMPORT script_suppressor
17 18
 * IMPORT is_chrome
18 19
 * IMPORT is_mozilla
20
 * IMPORT start_activity_info_server
19 21
 * IMPORTS_END
20 22
 */
21 23

  
......
35 37

  
36 38
const suppressor = script_suppressor(unique);
37 39

  
38
function needs_blocking()
40

  
41
function is_http()
39 42
{
40
    if (url.startsWith("https://") || url.startsWith("http://"))
41
	return false;
43
    return !!/^https?:\/\//i.exec(document.URL);
44
}
42 45

  
46
function is_whitelisted()
47
{
43 48
    const parsed_url = url_extract_target(document.URL);
44 49

  
45 50
    if (parsed_url.target !== undefined &&
......
49 54
	else
50 55
	    history.replaceState(null, "", parsed_url.base_url);
51 56

  
52
	console.log(["allowing whitelisted", document.URL]);
53
	return false;
57
	return true;
54 58
    }
55 59

  
56
    console.log(["disallowing", document.URL]);
57
    return true;
60
    return false;
58 61
}
59 62

  
60 63
function handle_mutation(mutations, observer)
......
120 123
	head.insertBefore(meta, head.firstElementChild);
121 124
}
122 125

  
123
if (needs_blocking()) {
124
    block_nodes_recursively(document.documentElement);
125

  
126
    if (is_chrome) {
127
	var observer = new MutationObserver(handle_mutation);
128
	observer.observe(document.documentElement, {
129
	    attributes: true,
130
	    childList: true,
131
	    subtree: true
132
	});
126
if (!is_privileged_url(document.URL)) {
127
    start_activity_info_server();
128
    handle_page_actions(unique);
129

  
130
    if (is_http()) {
131
	/* rely on CSP injected through webRequest */
132
    } else if (is_whitelisted()) {
133
	/* do not block scripts at all */
134
    } else {
135
	block_nodes_recursively(document.documentElement);
136

  
137
	if (is_chrome) {
138
	    var observer = new MutationObserver(handle_mutation);
139
	    observer.observe(document.documentElement, {
140
		attributes: true,
141
		childList: true,
142
		subtree: true
143
	    });
144
	}
145

  
146
	if (is_mozilla)
147
	    addEventListener('beforescriptexecute', suppressor, true);
133 148
    }
134

  
135
    if (is_mozilla)
136
	addEventListener('beforescriptexecute', suppressor, true);
137 149
}
138

  
139
handle_page_actions(unique);
content/page_actions.js
9 9
 * IMPORTS_START
10 10
 * IMPORT CONNECTION_TYPE
11 11
 * IMPORT browser
12
 * IMPORT report_script
13
 * IMPORT report_settings
12 14
 * IMPORTS_END
13 15
 */
14 16

  
......
19 21

  
20 22
function handle_message(message)
21 23
{
22
    if (message.inject === undefined)
23
	return;
24
    const [action, data] = message;
24 25

  
25
    for (let script_text of message.inject) {
26
	if (loaded)
27
	    add_script(script_text);
28
	else
29
	    scripts_awaiting.push(script_text);
26
    if (action === "inject") {
27
	for (let script_text of data) {
28
	    if (loaded)
29
		add_script(script_text);
30
	    else
31
		scripts_awaiting.push(script_text);
32
	}
30 33
    }
34
    if (action === "settings")
35
	report_settings(data);
31 36
}
32 37

  
33 38
function document_loaded(event)
......
46 51
    script.textContent = script_text;
47 52
    script.setAttribute("nonce", nonce);
48 53
    document.body.appendChild(script);
54

  
55
    report_script(script_text);
49 56
}
50 57

  
51 58
function handle_page_actions(script_nonce) {
html/display-panel.html
6 6
<html>
7 7
  <head>
8 8
    <meta charset="utf-8"/>
9
    <title>Myext popup</title>
9
    <title>Hachette - page settings</title>
10
    <style>
11
      input[type="radio"], input[type="checkbox"] {
12
	  display: none;
13
      }
14

  
15
      body {
16
	  width: 300px;
17
	  height: 300px;
18
      }
19

  
20
      .show_next:not(:checked)+* {
21
	  display: none;
22
      }
23

  
24
      .hide {
25
	  display: none;
26
      }
27

  
28
      #possible_patterns_chbx:not(:checked)+label span#triangle:first-child+span,
29
      #possible_patterns_chbx:not(:checked)+label+*,
30
      #possible_patterns_chbx:checked+label span#triangle:first-child {
31
	  display: none;
32
      }
33

  
34
      #container_for_injected>#none_injected:not(:last-child) {
35
	  display: none;
36
      }
37

  
38
      input#connected_chbx:checked+div+h3 {
39
	  display: none;
40
      }
41
    </style>
10 42
  </head>
11 43
  <body>
12
    <button id="settings_but" type="button">Settings</button>_POPUPSCRIPTS_
44
    <!-- The invisible div below is for elements that will be cloned. -->
45
    <div class="hide">
46
      <li id="pattern_li_template">
47
	<span></span>
48
	<button>View in settings</button>
49
      </li>
50
    </div>
51

  
52
    <h2 id="page_url_heading"></h2>
53

  
54
    <input id="show_privileged_notice_chbx" type="checkbox" class="show_next"></input>
55
    <h3>Privileged page</h3>
56

  
57
    <input id="show_page_state_chbx" type="checkbox" class="show_next"></input>
58
    <div>
59
      <input id="possible_patterns_chbx" type="checkbox"></input>
60
      <label for="possible_patterns_chbx">
61
	<h3>
62
	  <span id="triangle">&#x23F5;</span><span>&#x23F7;</span>
63
	  Possible patterns
64
	</h3>
65
      </label>
66
      <ul id="possible_patterns"></ul>
67

  
68
      <input id="connected_chbx" type="checkbox" class="show_next"></input>
69
      <div>
70
	<h3>
71
	  Matched pattern: <span id="pattern">...</span>
72
	  <button id="view_pattern" class="hide">
73
	  View in settings
74
	  </button>
75
	</h3>
76
	<h3>
77
	  Blocked: <span id="blocked">...</span>
78
	</h3>
79
	<h3>
80
	  Payload: <span id="payload">...</span>
81
	  <button id="view_payload" class="hide">
82
	    View in settings
83
	  </button>
84
	</h3>
85
	<h3>Injected</h3>
86
	<div id="container_for_injected">
87
	  <span id="none_injected">None</span>
88
	</div>
89
      </div>
90
      <h3>Trying to connect..<input id="loading_chbx" type="checkbox" class="show_next"></input><span>.</span></h3>
91
    </div>
92

  
93
    <button id="settings_but" type="button" style="margin-top: 20px;">Settings</button>_POPUPSCRIPTS_
13 94
  </body>
14 95
</html>
html/display-panel.js
8 8
/*
9 9
 * IMPORTS_START
10 10
 * IMPORT browser
11
 * IMPORT is_chrome
12
 * IMPORT is_mozilla
13
 * IMPORT CONNECTION_TYPE
14
 * IMPORT url_item
15
 * IMPORT is_privileged_url
16
 * IMPORT TYPE_PREFIX
17
 * IMPORT nice_name
18
 * IMPORT open_in_settings
19
 * IMPORT for_each_possible_pattern
11 20
 * IMPORTS_END
12 21
 */
13 22

  
14
document.getElementById("settings_but")
23
function by_id(id)
24
{
25
    return document.getElementById(id);
26
}
27

  
28
const tab_query = {currentWindow: true, active: true};
29

  
30
async function get_current_tab()
31
{
32
    /* Fix for fact that Chrome does not use promises here */
33
    const promise = is_chrome ?
34
	  new Promise((resolve, reject) =>
35
		      browser.tabs.query(tab_query, tab => resolve(tab))) :
36
	  browser.tabs.query(tab_query);
37

  
38
    try {
39
	return (await promise)[0];
40
    } catch(e) {
41
	console.log(e);
42
    }
43
}
44

  
45
const page_url_heading = by_id("page_url_heading");
46
const show_privileged_notice_chbx = by_id("show_privileged_notice_chbx");
47
const show_page_state_chbx = by_id("show_page_state_chbx");
48

  
49
async function show_page_activity_info()
50
{
51
    const tab = await get_current_tab();
52

  
53
    if (tab === undefined) {
54
	page_url_heading.textContent = "unknown page";
55
	return;
56
    }
57

  
58
    const url = url_item(tab.url);
59
    page_url_heading.textContent = url;
60
    if (is_privileged_url(url)) {
61
	show_privileged_notice_chbx.checked = true;
62
	return;
63
    }
64

  
65
    populate_possible_patterns_list(url);
66
    show_page_state_chbx.checked = true;
67

  
68
    try_to_connect(tab.id);
69
}
70

  
71
function populate_possible_patterns_list(url)
72
{
73
    for_each_possible_pattern(url, add_pattern_to_list);
74

  
75
    const port = browser.runtime.connect({name: CONNECTION_TYPE.PAGE_INFO});
76
    port.onMessage.addListener(handle_page_info);
77
    port.postMessage(["subscribe", url]);
78
}
79

  
80
const possible_patterns_ul = by_id("possible_patterns");
81
const pattern_li_template = by_id("pattern_li_template");
82
pattern_li_template.removeAttribute("id");
83
const known_patterns = new Map();
84

  
85
function add_pattern_to_list(pattern)
86
{
87
    const li = pattern_li_template.cloneNode(true);
88
    li.id = `pattern_li_${known_patterns.size}`;
89
    known_patterns.set(pattern, li.id);
90

  
91
    const span = li.firstElementChild;
92
    span.textContent = pattern;
93

  
94
    const button = span.nextElementSibling;
95
    const settings_opener = () => open_in_settings(TYPE_PREFIX.PAGE, pattern);
96
    button.addEventListener("click", settings_opener);
97

  
98
    possible_patterns_ul.appendChild(li)
99

  
100
    return li.id;
101
}
102

  
103
function ensure_pattern_exists(pattern)
104
{
105
    let id = known_patterns.get(pattern);
106
    /*
107
     * As long as pattern computation works well, we should never get into this
108
     * conditional block. This is just a safety measure. To be removed as part
109
     * of a bigger rework when we start taking iframes into account.
110
     */
111
    if (id === undefined) {
112
	console.log(`unknown pattern: ${pattern}`);
113
	id = add_pattern_to_list(pattern);
114
    }
115

  
116
    return id;
117
}
118

  
119
function set_pattern_li_button_text(li_id, text)
120
{
121
    by_id(li_id).firstElementChild.nextElementSibling.textContent = text;
122
}
123

  
124
function handle_page_info(message)
125
{
126
    const [type, data] = message;
127

  
128
    if (type === "change") {
129
	const li_id = ensure_pattern_exists(data.item);
130
	if (data.old_val === undefined)
131
	    set_pattern_li_button_text(li_id, "Edit in settings");
132
	if (data.new_val === undefined)
133
	    set_pattern_li_button_text(li_id, "Add setting");
134
    }
135

  
136
    if (type === "new_url") {
137
	for (const li_id of known_patterns.values())
138
	    set_pattern_li_button_text(li_id, "Add setting");
139
	for (const [pattern, settings] of data) {
140
	    set_pattern_li_button_text(ensure_pattern_exists(pattern),
141
				       "Edit in settings")
142
	}
143
    }
144
}
145

  
146
const connected_chbx = by_id("connected_chbx");
147

  
148
function try_to_connect(tab_id)
149
{
150
    /* This won't connect to iframes. We'll add support for them later */
151
    const connect_info = {name: CONNECTION_TYPE.ACTIVITY_INFO, frameId: 0};
152
    const port = browser.tabs.connect(tab_id, connect_info);
153

  
154
    port.onDisconnect.addListener(port => handle_disconnect(tab_id));
155
    port.onMessage.addListener(handle_activity_report);
156

  
157
    if (is_mozilla)
158
	setTimeout(() => monitor_connecting(port, tab_id), 1000);
159
}
160

  
161
const loading_chbx = by_id("loading_chbx");
162

  
163
function handle_disconnect(tab_id)
164
{
165
    if (is_chrome && !browser.runtime.lastError)
166
	return;
167

  
168
    /* return if there was no connection initialization failure */
169
    if (connected_chbx.checked)
170
	return;
171

  
172
    loading_chbx.checked = !loading_chbx.checked;
173
    setTimeout(() => try_to_connect(tab_id), 1000);
174
}
175

  
176
function monitor_connecting(port, tab_id)
177
{
178
    if (connected_chbx.checked)
179
	return;
180

  
181
    port.disconnect();
182
    loading_chbx.checked = !loading_chbx.checked;
183
    try_to_connect(tab_id);
184
}
185

  
186
const pattern_span = by_id("pattern");
187
const view_pattern_but = by_id("view_pattern");
188
const blocked_span = by_id("blocked");
189
const payload_span = by_id("payload");
190
const view_payload_but = by_id("view_payload");
191
const container_for_injected = by_id("container_for_injected");
192

  
193
function handle_activity_report(message)
194
{
195
    connected_chbx.checked = true;
196

  
197
    const [type, data] = message;
198

  
199
    if (type === "settings") {
200
	let [pattern, settings] = data;
201

  
202
	settings = settings || {};
203
	blocked_span.textContent = settings.allow ? "no" : "yes";
204

  
205
	if (pattern) {
206
	    pattern_span.textContent = pattern;
207
	    const settings_opener =
208
		  () => open_in_settings(TYPE_PREFIX.PAGE, pattern);
209
	    view_pattern_but.classList.remove("hide");
210
	    view_pattern_but.addEventListener("click", settings_opener);
211
	} else {
212
	    pattern_span.textContent = "none";
213
	}
214

  
215
	const components = settings.components;
216
	if (components) {
217
	    payload_span.textContent = nice_name(...components);
218
	    const settings_opener = () => open_in_settings(...components);
219
	    view_payload_but.classList.remove("hide");
220
	    view_payload_but.addEventListener("click", settings_opener);
221
	} else {
222
	    payload_span.textContent = "none";
223
	}
224
    }
225
    if (type === "script") {
226
	const h4 = document.createElement("h4");
227
	const pre = document.createElement("pre");
228
	h4.textContent = "script";
229
	pre.textContent = data;
230

  
231
	container_for_injected.appendChild(h4);
232
	container_for_injected.appendChild(pre);
233
    }
234
}
235

  
236
by_id("settings_but")
15 237
    .addEventListener("click", (e) => browser.runtime.openOptionsPage());
238

  
239
show_page_activity_info();
html/options_main.js
12 12
 * IMPORT TYPE_NAME
13 13
 * IMPORT list_prefixes
14 14
 * IMPORT url_extract_target
15
 * IMPORT nice_name
15 16
 * IMPORTS_END
16 17
 */
17 18

  
......
21 22
    return document.getElementById(id);
22 23
}
23 24

  
24
function nice_name(prefix, name)
25
{
26
    return `${name} (${TYPE_NAME[prefix]})`;
27
}
28

  
29 25
const item_li_template = by_id("item_li_template");
30 26
const bag_component_li_template = by_id("bag_component_li_template");
31 27
const chbx_component_li_template = by_id("chbx_component_li_template");

Also available in: Unified diff