/** * 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
 * proprietary program, I am not going to enforce this in court.
 */

/*
 * IMPORTS_START
 * IMPORT handle_page_actions
 * IMPORT gen_nonce
 * IMPORT is_privileged_url
 * IMPORT browser
 * IMPORT is_chrome
 * IMPORT is_mozilla
 * IMPORT start_activity_info_server
 * IMPORT make_csp_rule
 * IMPORT csp_header_regex
 * IMPORT report_settings
 * IMPORTS_END
 */

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 `