Revision 261548ff
Added by koszko about 2 years ago
| content/main.js | ||
|---|---|---|
| 5 | 5 | * Redistribution terms are gathered in the `copyright' file. | 
| 6 | 6 | */ | 
| 7 | 7 |  | 
| 8 | "use strict"; | |
| 8 | /* | |
| 9 | * IMPORTS_START | |
| 10 | * IMPORT handle_page_actions | |
| 11 | * IMPORT url_item | |
| 12 | * IMPORT gen_unique | |
| 13 | * IMPORT sanitize_attributes | |
| 14 | * IMPORT script_suppressor | |
| 15 | * IMPORT is_chrome | |
| 16 | * IMPORT is_mozilla | |
| 17 | * IMPORTS_END | |
| 18 | */ | |
| 9 | 19 |  | 
| 10 | (() => {
 | |
| 11 | const handle_page_actions = window.handle_page_actions; | |
| 12 | const url_item = window.url_item; | |
| 13 | const gen_unique = window.gen_unique; | |
| 14 | const sanitize_attributes = window.sanitize_attributes; | |
| 20 | /* | |
| 21 | * Due to some technical limitations the chosen method of whitelisting sites | |
| 22 | * is to smuggle whitelist indicator in page's url as a "magical" string | |
| 23 | * after '#'. Right now this is not needed in HTTP(s) pages where native | |
| 24 | * script blocking happens through CSP header injection but is needed for | |
| 25 | * protocols like ftp:// and file://. | |
| 26 | * | |
| 27 | * The code that actually injects the magical string into ftp:// and file:// | |
| 28 | * urls has not yet been added to the extension. | |
| 29 | */ | |
| 15 | 30 |  | 
| 16 | /* | |
| 17 | * Due to some technical limitations the chosen method of whitelisting sites | |
| 18 | * is to smuggle whitelist indicator in page's url as a "magical" string | |
| 19 | * after '#'. Right now this is not needed in HTTP(s) pages where native | |
| 20 | * script blocking happens through CSP header injection but is needed for | |
| 21 | * protocols like ftp:// and file://. | |
| 22 | * | |
| 23 | * The code that actually injects the magical string into ftp:// and file:// | |
| 24 | * urls has not yet been added to the extension. | |
| 25 | */ | |
| 31 | let url = url_item(document.URL); | |
| 32 | let unique = gen_unique(url); | |
| 33 | let nonce = unique.substring(1); | |
| 26 | 34 |  | 
| 27 | let url = url_item(document.URL); | |
| 28 | let unique = gen_unique(url); | |
| 29 | let nonce = unique.substring(1); | |
| 30 |  | |
| 31 | const scriptSuppressor = window.scriptSuppressor(nonce); | |
| 32 |  | |
| 33 | function needs_blocking() | |
| 34 |     {
 | |
| 35 | 	if (url.startsWith("https://") || url.startsWith("http://"))
 | |
| 36 | return false; | |
| 37 |  | |
| 38 | let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; | |
| 39 | let match = url_re.exec(document.URL); | |
| 40 | let base_url = match[1]; | |
| 41 | let first_target = match[3]; | |
| 42 | let second_target = match[4]; | |
| 43 |  | |
| 44 | if (first_target !== undefined && | |
| 45 | 	    first_target === unique) {
 | |
| 46 | if (second_target !== undefined) | |
| 47 | window.location.href = base_url + second_target; | |
| 48 | else | |
| 49 | history.replaceState(null, "", base_url); | |
| 50 |  | |
| 51 | console.log(["allowing whitelisted", document.URL]); | |
| 52 | return false; | |
| 53 | } | |
| 54 |  | |
| 55 | console.log(["disallowing", document.URL]); | |
| 56 | return true; | |
| 57 | } | |
| 35 | const suppressor = script_suppressor(nonce); | |
| 36 |  | |
| 37 | function needs_blocking() | |
| 38 | {
 | |
| 39 |     if (url.startsWith("https://") || url.startsWith("http://"))
 | |
| 40 | return false; | |
| 58 | 41 |  | 
| 59 | function handle_mutation(mutations, observer) | |
| 60 |     {
 | |
| 61 | 	if (document.readyState === 'complete') {
 | |
| 62 | 	    console.log("complete");
 | |
| 63 | observer.disconnect(); | |
| 64 | return; | |
| 65 | } | |
| 66 | 	for (let mutation of mutations) {
 | |
| 67 | 	    for (let node of mutation.addedNodes) {
 | |
| 68 | /* | |
| 69 | * Modifying <script> element doesn't always prevent its | |
| 70 | * execution in some Mozilla browsers. Additional blocking | |
| 71 | * through CSP meta tag injection is required. | |
| 72 | */ | |
| 73 | 		if (node.tagName === "SCRIPT") {
 | |
| 74 | block_script(node); | |
| 75 | continue; | |
| 76 | } | |
| 77 |  | |
| 78 | sanitize_attributes(node); | |
| 79 |  | |
| 80 | if (node.tagName === "HEAD") | |
| 81 | inject_csp(node); | |
| 82 | } | |
| 83 | } | |
| 42 | let url_re = /^([^#]*)((#[^#]*)(#.*)?)?$/; | |
| 43 | let match = url_re.exec(document.URL); | |
| 44 | let base_url = match[1]; | |
| 45 | let first_target = match[3]; | |
| 46 | let second_target = match[4]; | |
| 47 |  | |
| 48 | if (first_target !== undefined && | |
| 49 | 	first_target === unique) {
 | |
| 50 | if (second_target !== undefined) | |
| 51 | window.location.href = base_url + second_target; | |
| 52 | else | |
| 53 | history.replaceState(null, "", base_url); | |
| 54 |  | |
| 55 | console.log(["allowing whitelisted", document.URL]); | |
| 56 | return false; | |
| 84 | 57 | } | 
| 85 | 58 |  | 
| 86 | function block_script(node) | |
| 87 |     {
 | |
| 88 | console.log(node); | |
| 89 |  | |
| 90 | /* | |
| 91 | * Disabling scripts this way allows them to still be relatively | |
| 92 | * easily accessed in case they contain some useful data. | |
| 93 | */ | |
| 94 | 	if (node.hasAttribute("type"))
 | |
| 95 | 	    node.setAttribute("blocked-type", node.getAttribute("type"));
 | |
| 96 | 	node.setAttribute("type", "application/json");
 | |
| 59 | console.log(["disallowing", document.URL]); | |
| 60 | return true; | |
| 61 | } | |
| 62 |  | |
| 63 | function handle_mutation(mutations, observer) | |
| 64 | {
 | |
| 65 |     if (document.readyState === 'complete') {
 | |
| 66 | 	console.log("mutation handling complete");
 | |
| 67 | observer.disconnect(); | |
| 68 | return; | |
| 97 | 69 | } | 
| 70 |     for (const mutation of mutations) {
 | |
| 71 | for (const node of mutation.addedNodes) | |
| 72 | block_node(node); | |
| 73 | } | |
| 74 | } | |
| 75 |  | |
| 76 | function block_nodes_recursively(node) | |
| 77 | {
 | |
| 78 | block_node(node); | |
| 79 | for (const child of node.children) | |
| 80 | block_nodes_recursively(child); | |
| 81 | } | |
| 98 | 82 |  | 
| 99 |     function inject_csp(node)
 | |
| 100 |     {
 | |
| 101 | 	console.log('injecting CSP');
 | |
| 102 | 	let meta = document.createElement("meta");
 | |
| 103 | 	meta.setAttribute("http-equiv", "Content-Security-Policy");
 | |
| 104 | 	meta.setAttribute("content", `\
 | |
| 105 | script-src 'nonce-${nonce}'; \
 | |
| 106 | script-src-elem 'nonce-${nonce}';\
 | |
| 107 | `);
 | |
| 108 | 	node.appendChild(meta);
 | |
| 83 | function block_node(node)
 | |
| 84 | {
 | |
| 85 |     /*
 | |
| 86 |      * Modifying <script> element doesn't always prevent its
 | |
| 87 |      * execution in some Mozilla browsers. Additional blocking
 | |
| 88 |      * through CSP meta tag injection is required.
 | |
| 89 |      */
 | |
| 90 |     if (node.tagName === "SCRIPT") {
 | |
| 91 | 	block_script(node);
 | |
| 92 | 	return;
 | |
| 109 | 93 | } | 
| 110 | 94 |  | 
| 111 |     if (needs_blocking()) {
 | |
| 112 | // Script blocking for Gecko | |
| 113 | 	addEventListener('beforescriptexecute', scriptSuppressor, true);
 | |
| 114 |  | |
| 95 | sanitize_attributes(node); | |
| 96 |  | |
| 97 | if (node.tagName === "HEAD") | |
| 98 | inject_csp(node); | |
| 99 | } | |
| 100 |  | |
| 101 | function block_script(node) | |
| 102 | {
 | |
| 103 | /* | |
| 104 | * Disabling scripts this way allows them to still be relatively | |
| 105 | * easily accessed in case they contain some useful data. | |
| 106 | */ | |
| 107 |     if (node.hasAttribute("type"))
 | |
| 108 | 	node.setAttribute("blocked-type", node.getAttribute("type"));
 | |
| 109 |     node.setAttribute("type", "application/json");
 | |
| 110 | } | |
| 111 |  | |
| 112 | function inject_csp(head) | |
| 113 | {
 | |
| 114 |     console.log('injecting CSP');
 | |
| 115 |  | |
| 116 |     let meta = document.createElement("meta");
 | |
| 117 |     meta.setAttribute("http-equiv", "Content-Security-Policy");
 | |
| 118 |  | |
| 119 |     let rule = `script-src 'nonce-${nonce}'; `;
 | |
| 120 | if (is_chrome) | |
| 121 | 	rule += `script-src-elem 'nonce-${nonce}';`;
 | |
| 122 |  | |
| 123 |     meta.setAttribute("content", rule);
 | |
| 124 |  | |
| 125 | if (head.firstElementChild === null) | |
| 126 | head.appendChild(meta); | |
| 127 | else | |
| 128 | head.insertBefore(meta, head.firstElementChild); | |
| 129 | } | |
| 130 |  | |
| 131 | if (needs_blocking()) {
 | |
| 132 | block_nodes_recursively(document.documentElement); | |
| 133 |  | |
| 134 |     if (is_chrome) {
 | |
| 115 | 135 | var observer = new MutationObserver(handle_mutation); | 
| 116 | 136 | 	observer.observe(document.documentElement, {
 | 
| 117 | 137 | attributes: true, | 
| ... | ... | |
| 120 | 140 | }); | 
| 121 | 141 | } | 
| 122 | 142 |  | 
| 123 | handle_page_actions(nonce); | |
| 124 | })(); | |
| 143 | if (is_mozilla) | |
| 144 | 	addEventListener('beforescriptexecute', suppressor, true);
 | |
| 145 | } | |
| 146 |  | |
| 147 | handle_page_actions(nonce); | |
Also available in: Unified diff
emply an sh-based build system; make some changes to blocking