Skip to content

Commit

Permalink
extension/*: implement config.replaySubmitHTTP and replay* popup …
Browse files Browse the repository at this point in the history
…UI buttons, keyboard shortcuts, and context menu items

Also, document them.
  • Loading branch information
oxij committed Dec 20, 2024
1 parent 0dfe9d6 commit 0d01f90
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 14 deletions.
124 changes: 118 additions & 6 deletions extension/background/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ let configDefaults = {
exportAsInFlightTimeout: 60,
gzipExportAs: true,

// submission via HTTP
// submission and replay via HTTP
archiveSubmitHTTP: false,
replaySubmitHTTP: null,
submitHTTPURLBase: "http://127.0.0.1:3210/",

// saving to local storage
Expand Down Expand Up @@ -150,6 +151,7 @@ let configDefaults = {

root: {
snapshottable: true,
replayable: true,
workOffline: false,
collecting: true,
problematicNotify: true,
Expand Down Expand Up @@ -221,6 +223,7 @@ let serverConfigDefaults = {
dump_wrr: "/pwebarc/dump",
},
canDump: false,
canReplay: false,
};
let serverConfig = assignRec({}, serverConfigDefaults);

Expand Down Expand Up @@ -303,6 +306,7 @@ async function checkServer() {

serverConfig.info = info;
serverConfig.canDump = info.dump_wrr !== undefined;
serverConfig.canReplay = info.replay_latest !== undefined;

if (!serverConfig.alive) {
await browser.notifications.create("error-server", {
Expand Down Expand Up @@ -338,6 +342,51 @@ async function checkServer() {
retryOneUnarchived(config.submitHTTPURLBase, true);
}

function checkReplay() {
const ifFixed = `\n\nIf you fixed it and the error persists, press the "Retry … failed" button in the popup.`;

if (config.replaySubmitHTTP === false) {
browser.notifications.create(`error-replay`, {
title: "Hoardy-Web: ERROR",
message: escapeNotification(config, `Replay is forbidden by the "Replay from the archiving server" option.\n\nEnable it to allow this feature.`),
iconUrl: iconURL("error", 128),
type: "basic",
}).catch(logError);

return false;
} else if (!serverConfig.alive) {
browser.notifications.create(`error-replay`, {
title: "Hoardy-Web: ERROR",
message: escapeNotification(config, `Replay is impossible because the archiving server at \`${serverConfig.baseURL}\` is unavailable or defunct.` + ifFixed),
iconUrl: iconURL("error", 128),
type: "basic",
}).catch(logError);

return false;
} else if (!serverConfig.canReplay) {
browser.notifications.create(`error-replay`, {
title: "Hoardy-Web: ERROR",
message: escapeNotification(config, `The archiving server at \`${serverConfig.baseURL}\` does not support replay.\n\nSwitch your archiving server to \`hoardy-web serve\` for this feature to work.` + ifFixed),
iconUrl: iconURL("error", 128),
type: "basic",
}).catch(logError);

return false;
} else
// clear stale
browser.notifications.clear("error-replay").catch(logError);

return true;
}

function latestReplayOf(url) {
if (!serverConfig.canReplay)
throw Error("replay is not available");

let replayURL = serverConfig.info.replay_latest.replace("{url}", url);
return (new URL(replayURL, serverConfig.baseURL)).href;
}

function isServerURL(url) {
return url.startsWith(serverConfig.baseURL);
}
Expand Down Expand Up @@ -944,6 +993,7 @@ function getStats() {
bundledAs_size: bundledAsSize,
submittedHTTP: globals.submittedHTTPTotal,
submittedHTTP_size: globals.submittedHTTPSize,
can_replay: serverConfig.canReplay,
saved: globals.savedLS.number + globals.savedIDB.number,
saved_size: globals.savedLS.size + globals.savedIDB.size,
unarchived: archiveFailed,
Expand Down Expand Up @@ -3321,6 +3371,32 @@ async function snapshot(tabIdNull) {
scheduleEndgame(tabIdNull);
}

async function replay(tabIdNull, direction) {
if (!checkReplay())
return;

let tabs;
if (tabIdNull === null)
tabs = await browser.tabs.query({});
else {
let tab = await browser.tabs.get(tabIdNull);
tabs = [ tab ];
}

for (let tab of tabs) {
let tabId = tab.id;
let tabcfg = getOriginConfig(tabId);
let url = getTabURL(tab);
if (tabIdNull === null && !tabcfg.replayable
|| isBoringOrServerURL(url)) {
if (config.debugging)
console.log("NOT replaying tab", tabId, url);
continue;
}
await navigateTabTo(tabId, latestReplayOf(url));
}
}

function emitTabInFlightWebRequest(tabId, reason) {
for (let [requestId, reqres] of Array.from(reqresInFlight.entries())) {
if (tabId === null || reqres.tabId === tabId)
Expand Down Expand Up @@ -4290,6 +4366,9 @@ function handleMessage(request, sender, sendResponse) {
case "snapshot":
snapshot(arg1);
break;
case "replay":
replay(arg1, arg2);
break;
case "runActions":
syncRunActions();
scheduleEndgame(null);
Expand Down Expand Up @@ -4360,6 +4439,18 @@ function initMenus() {
title: menuTitleWindow[true],
});

browser.menus.create({
id: "replay-tab",
contexts: ["link"],
title: "Replay Link in New Tab",
});

browser.menus.create({
id: "replay-window",
contexts: ["link"],
title: "Replay Link in New Window",
});

if (!useDebugger) {
browser.menus.update("open-not-tab", { icons: menuIcons[true] });
browser.menus.update("open-not-window", { icons: menuIcons[true] });
Expand All @@ -4376,14 +4467,21 @@ function initMenus() {
if (config.debugging)
console.log("menu action", info, tab);

let cmd = info.menuItemId;
let url = info.linkUrl;
let newWindow = info.menuItemId === "open-not-window"
let newWindow = cmd.endsWith("-window")
&& (url.startsWith("http:") || url.startsWith("https:"));

negateConfigFor.add(tab.id);
if (useDebugger)
// work around Chromium bug
negateOpenerTabIds.push(tab.id);
if (cmd.startsWith("replay-")) {
if (!checkReplay())
return;
url = latestReplayOf(url);
} else if (cmd.startsWith("open-not-")) {
negateConfigFor.add(tab.id);
if (useDebugger)
// work around Chromium bug
negateOpenerTabIds.push(tab.id);
}

browser.tabs.create({
url,
Expand Down Expand Up @@ -4451,6 +4549,15 @@ async function handleCommand(command) {
case "snapshotTab":
snapshot(tabId);
return;
case "replayAll":
replay(null, null);
return;
case "replayTabBack":
replay(tabId, false);
return;
case "replayTabForward":
replay(tabId, true);
return;
case "toggleTabConfigSnapshottable":
tabcfg = getOriginConfig(tabId);
tabcfg.snapshottable = !tabcfg.snapshottable;
Expand Down Expand Up @@ -4619,6 +4726,11 @@ function fixConfig(config, oldConfig) {
syncRetryUnarchived(true);
wantArchiveDoneNotify = true;
}

if (config.replaySubmitHTTP !== false
&& (config.replaySubmitHTTP !== oldConfig.replaySubmitHTTP
|| config.submitHTTPURLBase !== oldConfig.submitHTTPURLBase))
wantCheckServer = true;
}

function upgradeConfig(config) {
Expand Down
6 changes: 6 additions & 0 deletions extension/manifest-common.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@
},
"snapshotTab": {
"description": "Snapshot Tab: Take DOM snapshots of all frames of the currently active tab"
},
"replayAll": {
"description": "Replay All: If the archiving server supports replay, re-navigate all tabs that finished loading to their replayed versions"
},
"replayTabBack": {
"description": "Replay Tab: If the archiving server supports replay and the currently active tab has finished loading, re-navigate it to its replayed version"
}
}
}
28 changes: 26 additions & 2 deletions extension/page/help.org
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,14 @@ By default, "Work offline" mode is enabled for =file:= URLs to stop any pages ge

- On Firefox-based browsers, you can see and edit all keyboard shortcuts via =Add-ons and themes= (=about:addons=) -> the gear icon -> =Manage Extension Shortcuts=.
- On Chromium-based browsers, you can see and edit all keyboard shortcuts via the menu -> =Extensions= -> =Manage Extensions= (=chrome://extensions/=) -> =Keyboard shortcuts= (on the left).
** Replay buttons
:PROPERTIES:
:CUSTOM_ID: replay
:END:

When your [[./popup.html#div-config.submitHTTPURLBase][archiving server]] supports it and [[./popup.html#div-config.replaySubmitHTTP][this option]] is enabled, =Hoardy-Web= enables its integration with replay over =HTTP=.

At the moment, this includes [[./popup.html#replayAll][two]] [[./popup.html#replayTabBack][buttons]] which re-navigate all tabs or the currently active tab (respectively) to their replay pages as well as keyboard shortcuts and context menu actions described below.
** Keyboard shortcuts
:PROPERTIES:
:CUSTOM_ID: keyboard-shortcuts
Expand Down Expand Up @@ -534,14 +542,16 @@ By default, "Work offline" mode is enabled for =file:= URLs to stop any pages ge
- [[./popup.html#discardAllTabInLimbo][discard all reqres from limbo for the currently active tab]], {{{shortcut(discardAllTabInLimbo)}}};
- [[./popup.html#snapshotAll][take =DOM= snapshot of all tabs]] for which [[./popup.html#div-tabconfig.snapshottable][=Include in global snapshots= setting]] is enabled, {{{shortcut(snapshotAll)}}};
- [[./popup.html#snapshotTab][take =DOM= snapshot of the currently active tab]], {{{shortcut(snapshotTab)}}}.
- [[./popup.html#replayAll][replay all tabs]] for which [[./popup.html#div-tabconfig.replayable][=Include in global replays= setting]] is enabled, {{{shortcut(replayAll)}}};
- [[./popup.html#replayTabBack][replay the currently active tab]], {{{shortcut(replayTabBack)}}}.
** Context menu actions
:PROPERTIES:
:CUSTOM_ID: context-menu-shortcuts
:END:

=Hoardy-Web= provides context menu actions to:

- open a given link in a new tab with currently active tab's [[./popup.html#div-tabconfig.children.collecting][tracking in children tabs setting]] negated.
- open a link in a new tab with currently active tab's [[./popup.html#div-tabconfig.children.collecting][tracking in children tabs setting]] negated.
I.e.,

- right-mouse clicking while pointing at a link and
Expand All @@ -553,7 +563,9 @@ By default, "Work offline" mode is enabled for =file:= URLs to stop any pages ge
- middle-mouse clicking a link,
- toggling [[./popup.html#div-tabconfig.children.collecting][this]] again.

- do the same thing, but opening it in a new window.
- do the same thing, but opening it in a new window;

- open a replay of a link in a new tab.
* Error messages and codes
:PROPERTIES:
:CUSTOM_ID: errors
Expand All @@ -571,6 +583,18 @@ By default, "Work offline" mode is enabled for =file:= URLs to stop any pages ge

If you fixed it and the error persists, press [[./popup.html#retryFailed][this button]].

- =Replay is forbidden by the "Replay from the archiving server" option.=

Un-disable [[./popup.html#div-config.replaySubmitHTTP][this option]].

- =Replay is impossible because the archiving server at `<URL>` is unavailable or defunct.= and =The archiving server at `<URL>` does not support replay.=

Are you running [[https://oxij.org/software/hoardy-web/tree/master/tool/][=hoardy-web serve=]]?

At the moment, that's the only archiving server that supports this.

If you fixed it and the error persists, press [[./popup.html#retryFailed][this button]].

- =Failed to archive <N> items because requests to the archiving server failed with: <STATUS> <REASON>: <RESPONSE>=

Your archiving sever is returning =HTTP= errors when =Hoardy-Web= is trying to archive data to it.
Expand Down
13 changes: 12 additions & 1 deletion extension/page/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ async function popupMain() {
let emojiButtons = {
reloadSelf: "(🌟ω🌟)",
snapshotAll: "📸",
replayAll: "⏏",
forgetHistory: "🧹",
showState: "📜",
runActions: "🟢",
Expand All @@ -138,6 +139,8 @@ async function popupMain() {
unmarkAllProblematic: "🧹",
stopAllInFlight: "⏹",
snapshotTab: "📸",
replayTabBack: "⏮",
//replayTabForward: "⏭",
forgetTabHistory: "🧹",
showTabState: "📜",
collectAllTabInLimbo: "✔",
Expand Down Expand Up @@ -216,6 +219,7 @@ async function popupMain() {
buttonToAction("showState", catchAll(() => replaceWith(showState, "", "top")));
buttonToMessage("forgetHistory", () => ["forgetHistory", null]);
buttonToMessage("snapshotAll", () => ["snapshot", null]);
buttonToMessage("replayAll", () => ["replay", null, null]);
buttonToMessage("exportAsAll", () => ["exportAs", null]);
buttonToMessage("collectAllInLimbo", () => ["popInLimbo", true, null, null]);
buttonToMessage("discardAllInLimbo", () => ["popInLimbo", false, null, null]);
Expand All @@ -225,6 +229,8 @@ async function popupMain() {
buttonToAction("showTabState", catchAll(() => replaceWith(showState, `?tab=${tabId}`, "top")));
buttonToMessage("forgetTabHistory", () => ["forgetHistory", tabId]);
buttonToMessage("snapshotTab", () => ["snapshot", tabId]);
buttonToMessage("replayTabBack", () => ["replay", tabId, false]);
//buttonToMessage("replayTabForward", () => ["replay", tabId, true]);
buttonToMessage("collectAllTabInLimbo", () => ["popInLimbo", true, null, tabId]);
buttonToMessage("discardAllTabInLimbo", () => ["popInLimbo", false, null, tabId]);
buttonToMessage("unmarkAllTabProblematic", () => ["unmarkProblematic", null, tabId]);
Expand Down Expand Up @@ -305,7 +311,8 @@ async function popupMain() {
implySetConditionalOff(dbody, "on-archive", !config.archive);
implySetConditionalOff(dbody, "on-exportAs", !(config.archive && config.archiveExportAs));
implySetConditionalOff(dbody, "on-exportAsBundle", !config.exportAsBundle);
implySetConditionalOff(dbody, "on-submitHTTP", !(config.archive && config.archiveSubmitHTTP));
implySetConditionalOff(dbody, "on-useHTTP", !(config.archive && config.archiveSubmitHTTP
|| config.replaySubmitHTTP));
implySetConditionalOff(dbody, "on-LS", !config.stash && !(config.archive && config.archiveSaveLS));
implySetConditionalOff(dbody, "on-auto", !config.autoUnmarkProblematic && !config.autoPopInLimboCollect && !config.autoPopInLimboDiscard);
implySetConditionalOff(dbody, "on-problematicNotify", !config.problematicNotify);
Expand All @@ -320,6 +327,7 @@ async function popupMain() {
setConditionalClass(reloadSelfButton, "attention", stats.update_available);
implySetConditionalClass(dbody, "on-reload", "hidden", !hash && !(stats.update_available || config.debugging));
implySetConditionalClass(dbody, "on-pending", "hidden", !stats.reload_pending);
implySetConditionalOff(dbody, "on-replay", !(config.replaySubmitHTTP !== false && stats.can_replay));
}

async function updateTabConfig(tabconfig) {
Expand All @@ -330,6 +338,9 @@ async function popupMain() {
case "tabconfig.snapshottable":
newtabconfig.children.snapshottable = newtabconfig.snapshottable;
break;
case "tabconfig.replayable":
newtabconfig.children.replayable = newtabconfig.replayable;
break;
case "tabconfig.workOffline":
if (config.workOfflineImpure)
newtabconfig.collecting = !newtabconfig.workOffline;
Expand Down
Loading

0 comments on commit 0d01f90

Please sign in to comment.