/**
* This file is part of Haketilo.
*
* Function: Main content script that runs in all frames.
*
* Copyright (C) 2021 Wojtek Kosior
* Copyright (C) 2021 jahoti
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute forms of that code without the copy of the GNU
* GPL normally required by section 4, provided you include this
* license notice and, in case of non-source distribution, a URL
* through which recipients can access the Corresponding Source.
* If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* As a special exception to the GPL, any HTML file which merely
* makes function calls to this code, and for that purpose
* includes it by reference shall be deemed a separate work for
* copyright law purposes. If you modify this code, you may extend
* this exception to your version of the code, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's
* license. Although I request that you do not make use this code in a
* proprietary program, I am not going to enforce this in court.
*/
#IMPORT content/activity_info_server.js
#FROM content/page_actions.js IMPORT handle_page_actions
#FROM common/misc.js IMPORT gen_nonce, is_privileged_url, \
csp_header_regex
#FROM common/browser.js IMPORT browser
/* CSP rule that blocks scripts according to policy's needs. */
function make_csp_rule(policy)
{
let rule = "prefetch-src 'none'; script-src-attr 'none';";
const script_src = policy.nonce !== undefined ?
`'nonce-${policy.nonce}'` : "'none'";
rule += ` script-src ${script_src}; script-src-elem ${script_src};`;
return rule;
}
document.content_loaded = document.readyState === "complete";
const wait_loaded = e => e.content_loaded ? Promise.resolve() :
new Promise(c => e.addEventListener("DOMContentLoaded", c, {once: true}));
wait_loaded(document).then(() => document.content_loaded = true);
/*
* In the case of HTML documents:
* 1. When injecting some payload we need to sanitize CSP tags before
* they reach the document.
* 2. Only tags inside
are considered valid by the browser and
* need to be considered.
* 3. We want to detach from document, wait until its completes
* loading, sanitize it and re-attach .
* 4. We shall wait for anything to appear in or after and take that as
* a sign has finished loading.
* 5. Otherwise, getting the `DOMContentLoaded' event on the document shall also
* be a sign that is fully loaded.
*/
function make_body_start_observer(DOM_element, waiting)
{
const observer = new MutationObserver(() => try_body_started(waiting));
observer.observe(DOM_element, {childList: true});
return observer;
}
function try_body_started(waiting)
{
const body = waiting.detached_html.querySelector("body");
if ((body && (body.firstChild || body.nextSibling)) ||
waiting.doc.documentElement.nextSibling) {
finish_waiting(waiting);
return true;
}
if (body && waiting.observers.length < 2)
waiting.observers.push(make_body_start_observer(body, waiting));
}
function finish_waiting(waiting)
{
if (waiting.finished)
return;
waiting.finished = true;
waiting.observers.forEach(observer => observer.disconnect());
setTimeout(waiting.callback, 0);
}
function _wait_for_head(doc, detached_html, callback)
{
const waiting = {doc, detached_html, callback, observers: []};
if (try_body_started(waiting))
return;
waiting.observers = [make_body_start_observer(detached_html, waiting)];
wait_loaded(doc).then(() => finish_waiting(waiting));
}
function wait_for_head(doc, detached_html)
{
return new Promise(cb => _wait_for_head(doc, detached_html, cb));
}
const blocked_str = "blocked";
function block_attribute(node, attr, ns=null)
{
const [hasa, geta, seta, rema] = ["has", "get", "set", "remove"]
.map(m => (n, ...args) => typeof ns === "string" ?
n[`${m}AttributeNS`](ns, ...args) : n[`${m}Attribute`](...args));
/*
* Disabling attributes by prepending `-blocked' allows them to still be
* relatively easily accessed in case they contain some useful data.
*/
const construct_name = [attr];
while (hasa(node, construct_name.join("")))
construct_name.unshift(blocked_str);
while (construct_name.length > 1) {
construct_name.shift();
const name = construct_name.join("");
seta(node, `${blocked_str}-${name}`, geta(node, name));
}
rema(node, attr);
}
/*
* Used to disable `