software/hoardy-web/./extension/background/reload.js

Passively capture, archive, and hoard your web browsing history, including the contents of the pages you visit, for later offline viewing, replay, mirroring, data scraping, and/or indexing. Your own personal private Wayback Machine that can also archive HTTP POST requests and responses, as well as most other HTTP-level data.

Files

Raw Source

Contents

/*
 * Copyright (c) 2023-2026 Jan Malakhovski <oxij@oxij.org>
 *
 * This file is a part of `hoardy-web` project.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Self-reload support.
 */

"use strict";

let wantReloadSelf = false;

async function performReloadSelf() {
    if (!wantReloadSelf) {
        return;
    }

    let badReqres =
        reqresUnstashedIssueAcc[0].size +
        // reqresUnarchivedIssueAcc[0].size // will be caught below
        reqresBuggedOutIssueAcc[0].size;

    if (badReqres > 0) {
        browser.notifications
            .create("error-noReload", {
                title: "Hoardy-Web: ERROR",
                message: escapeNotification(
                    config,
                    `\`Hoardy-Web\` can NOT be reloaded while some \`unstashed\` and/or \`buggedOut\` reqres are present.`,
                ),
                iconUrl: iconURL("error", 128),
                type: "basic",
            })
            .catch(logError);

        cancelReloadSelf();
        return;
    }

    let notDoneTasks =
        scheduledCancelable.size +
        // scheduledRetry is ignored here
        scheduledDelayed.size +
        scheduledSaveState.size +
        scheduledInternal.size +
        scheduledInternalCancelable.size +
        // scheduledHidden is ignored here
        synchronousClosuresA.length +
        synchronousClosuresB.length +
        synchronousClosuresC.length +
        scheduledWhenNoInFlight.size +
        scheduledWhenArchived.size +
        runningActions.size;

    if (notDoneTasks > 0) {
        console.warn("reload blocked by unfinished tasks");
        return;
    }

    let archivingReqres = config.archive ? reqresQueue.length : 0;
    let notDoneReqres = getInFlightNum(null) + reqresBundledAs.size + archivingReqres;

    if (notDoneReqres > 0) {
        console.warn("reload blocked by unfinished and/or unarchived reqres");
        return;
    }

    function isInSyncWithLS(archivable) {
        let loggable = archivable[0];
        return loggable.inLS !== undefined && !loggable.dirty;
    }

    let allInSyncWithLS =
        reqresLimbo.every(isInSyncWithLS) &&
        reqresQueue.every(isInSyncWithLS) &&
        Array.from(reqresUnarchivedIssueAcc[0]).every(isInSyncWithLS);

    if (!allInSyncWithLS) {
        console.warn("reload blocked by unstashed reqres");
        syncStashAll(true);
        scheduleEndgame(null);
        return;
    }

    console.warn("reloading!");

    let savedWindows = {};
    let savedTabs = {};

    let currentTabs = await browser.tabs.query({});
    for (let tab of currentTabs) {
        let windowId = tab.windowId;

        // biome-ignore lint/suspicious/noPrototypeBuiltins: skip
        if (!savedWindows.hasOwnProperty(windowId)) {
            savedWindows[windowId] = {
                cfg: windowConfig.get(windowId),
                state: windowState.get(windowId),
            };
        }

        let tabId = tab.id;
        savedTabs[tabId] = {
            url: getTabURL(tab),
            cfg: tabConfig.get(tabId),
            state: tabState.get(tabId),
        };
    }

    let session = {
        id: sessionId,
        windows: savedWindows,
        tabs: savedTabs,
        bg: tabState.get(-1),
        log: reqresLog,
        // queue and others are stashed
    };

    await browser.storage.local.set({ session });

    if (
        useDebugger &&
        currentTabs.every((tab) => tab.url === "about:blank" || isExtensionURL(tab.url))
    ) {
        // Chromium will close all such tabs on extension reload, meaning, in
        // this case, the whole browser window will close
        await browser.tabs.create({ url: "chrome://extensions/" });
    }

    browser.runtime.reload();
}

function reloadSelf() {
    wantReloadSelf = true;
    syncRunActions();
    // NB: keep this here here instead of wrapping this function using `runThenScheduleEndgame` so
    // that this function could be called from the debug console
    scheduleEndgame(null);
}

function cancelReloadSelf() {
    wantReloadSelf = false;
    // NB: similarly
    scheduleEndgame(null);
}