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.
/*
* Copyright (c) 2023-2025 Jan Malakhovski <oxij@oxij.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Utility functions for making in WebExtensions.
*
* Depends on `./base.js`, `./compat.js`, and `./ui.js`.
*/
"use strict";
async function getActiveTab() {
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
for (let tab of tabs) {
return tab;
}
return null;
}
function navigateTabTo(tabId, url) {
return browser.tabs.update(tabId, { url });
}
function navigateTabToBlank(tabId) {
return navigateTabTo(tabId, "about:blank");
}
function getTabURL(tab, def) {
let pendingUrl = tab.pendingUrl;
if (isDefinedURL(pendingUrl))
return pendingUrl;
let url = tab.url;
if (isDefinedURL(url))
return url;
return def;
}
async function getTabURLThenNavigateTabToBlank(tabId) {
let tab = await browser.tabs.get(tabId);
let url = getTabURL(tab);
await navigateTabToBlank(tabId);
return url;
}
async function getShortcuts() {
if (browser.commands === undefined)
return {};
let shortcuts = await browser.commands.getAll();
let res = {};
for (let s of shortcuts)
res[s.name] = s.shortcut;
return res;
}
function macroShortcuts(node, shortcuts, mapShortcutFunc) {
for (let child of node.childNodes) {
if (child.nodeName === "#text" || child.nodeName === "#comment") continue;
macroShortcuts(child, shortcuts, mapShortcutFunc);
}
let sname = node.getAttribute("data-macro-shortcut");
if (sname === null) return;
let shortcut = shortcuts[sname];
node.innerHTML = microMarkdownToHTML(mapShortcutFunc(node.innerText, shortcut, sname));
}
// make a DOM node with a given id emit a `browser.runtime.sendMessage` with the same id
function buttonToMessage(id, func) {
if (func === undefined)
return buttonToAction(id, catchAll(() => browser.runtime.sendMessage([id])));
else
return buttonToAction(id, catchAll(() => browser.runtime.sendMessage(func())));
}
// activate a tab with a given document URL if exists, or open new if not
async function spawnOrActivateTab(url, createProperties, currentWindow) {
if (currentWindow === undefined)
currentWindow = true;
let tabs = await browser.tabs.query({ currentWindow });
let nurl = normalizedURL(url);
for (let tab of tabs) {
if (normalizedURL(getTabURL(tab, "about:blank")) === nurl) {
// activate that tab instead
await browser.tabs.update(tab.id, { active: true });
return [tab, false];
}
}
// open new tab
let res = await browser.tabs.create(assignRec({ url }, createProperties || {}));
return [res, true];
}
async function showInternalPageAtNode(url, id, tabId, spawn, scrollIntoViewOptions) {
let rurl = url + (id ? "#" + id : "");
if (spawn === false) {
window.location = rurl;
return null;
} else {
let tab, spawned;
try {
[tab, spawned] = await spawnOrActivateTab(rurl, { openerTabId: tabId });
} catch (e) {
// in case tabId points to a dead tab
[tab, spawned] = await spawnOrActivateTab(rurl);
}
if (!spawned && id !== undefined)
broadcastToURL(false, url, "viewNode", id, scrollIntoViewOptions);
return tab.id;
}
}