tabctl 0.6.0-rc.10 → 0.6.0-rc.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extension/background.js +266 -22
- package/dist/extension/manifest.json +1 -1
- package/package.json +2 -2
|
@@ -783,7 +783,12 @@
|
|
|
783
783
|
var RECONNECT_ALARM = "tabctl-reconnect";
|
|
784
784
|
var KEEPALIVE_INTERVAL_MINUTES = 1;
|
|
785
785
|
var BROWSER_STATE_SYNC_DEBOUNCE_MS = 750;
|
|
786
|
-
var
|
|
786
|
+
var ACTIVE_PAGE_CACHE_LOADING_RETRY_MS = 100;
|
|
787
|
+
var ACTIVE_PAGE_CACHE_SETTLING_RETRY_MS = 150;
|
|
788
|
+
var ACTIVE_PAGE_CACHE_FIRST_QUIESCENT_TIMEOUT_MS = 750;
|
|
789
|
+
var ACTIVE_PAGE_CACHE_FIRST_QUIESCENT_SAMPLE_MS = 75;
|
|
790
|
+
var ACTIVE_PAGE_CACHE_FIRST_LOADING_MAX_ATTEMPTS = 300;
|
|
791
|
+
var ACTIVE_PAGE_CACHE_FIRST_SETTLING_MAX_ATTEMPTS = 200;
|
|
787
792
|
var ACTIVE_PAGE_CACHE_TIMEOUT_MS = 5e3;
|
|
788
793
|
var MAX_PAGE_HTML_CHARS = 10 * 1024 * 1024;
|
|
789
794
|
var ACTIVE_PAGE_CACHE_MAX_HTML_CHARS = MAX_PAGE_HTML_CHARS;
|
|
@@ -795,6 +800,10 @@
|
|
|
795
800
|
var ACTIVE_PAGE_CACHE_STATUS_TIMEOUT_MS = 3e4;
|
|
796
801
|
var CACHE_AVAILABLE_BADGE_TEXT = "C";
|
|
797
802
|
var CACHE_AVAILABLE_BADGE_COLOR = "#2da44e";
|
|
803
|
+
var CACHE_WAITING_BADGE_TEXT = "W";
|
|
804
|
+
var CACHE_WAITING_BADGE_COLOR = "#bf8700";
|
|
805
|
+
var CACHE_ERROR_BADGE_TEXT = "E";
|
|
806
|
+
var CACHE_ERROR_BADGE_COLOR = "#cf222e";
|
|
798
807
|
var RECONNECT_INITIAL_DELAY_MS = 250;
|
|
799
808
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
800
809
|
var RECONNECT_ALARM_MIN_DELAY_MS = 3e4;
|
|
@@ -832,7 +841,16 @@
|
|
|
832
841
|
lastCapturedKey: null,
|
|
833
842
|
lastQuiescentCapturedKey: null,
|
|
834
843
|
lastQuiescentCapturedAt: 0,
|
|
835
|
-
statusRequests: /* @__PURE__ */ new Map()
|
|
844
|
+
statusRequests: /* @__PURE__ */ new Map(),
|
|
845
|
+
states: /* @__PURE__ */ new Map()
|
|
846
|
+
};
|
|
847
|
+
var ACTIVE_PAGE_CACHE_STATE_DETAILS = {
|
|
848
|
+
checking: "checking status",
|
|
849
|
+
loading: "page still loading",
|
|
850
|
+
settling: "waiting for page settle",
|
|
851
|
+
capturing: "capture pending",
|
|
852
|
+
cached: "page cache available",
|
|
853
|
+
error: "capture failed"
|
|
836
854
|
};
|
|
837
855
|
function log(...args) {
|
|
838
856
|
console.log("[tabctl]", ...args);
|
|
@@ -969,6 +987,9 @@
|
|
|
969
987
|
function activePageCacheKey(tab, url) {
|
|
970
988
|
return `${tab.id}:${url}`;
|
|
971
989
|
}
|
|
990
|
+
function activePageCacheKeyForTabId(tabId, url) {
|
|
991
|
+
return `${tabId}:${url}`;
|
|
992
|
+
}
|
|
972
993
|
function activePageCacheUrl(tab) {
|
|
973
994
|
return tab.url || tab.pendingUrl || "";
|
|
974
995
|
}
|
|
@@ -987,19 +1008,84 @@
|
|
|
987
1008
|
log("clear cache indicator failed", { tabId, error });
|
|
988
1009
|
}
|
|
989
1010
|
}
|
|
990
|
-
async function
|
|
1011
|
+
async function setCacheBadgeIndicator(tabId, expectedUrl, text, color, title) {
|
|
991
1012
|
try {
|
|
992
1013
|
const tab = await chrome.tabs.get(tabId);
|
|
993
1014
|
if (!isEligibleActivePageCacheTab(tab) || activePageCacheUrl(tab) !== expectedUrl) {
|
|
994
1015
|
await clearCacheAvailableIndicator(tabId);
|
|
995
1016
|
return;
|
|
996
1017
|
}
|
|
997
|
-
await chrome.action?.setBadgeBackgroundColor?.({ tabId, color
|
|
998
|
-
await chrome.action?.setBadgeText?.({ tabId, text
|
|
999
|
-
await chrome.action?.setTitle?.({ tabId, title
|
|
1018
|
+
await chrome.action?.setBadgeBackgroundColor?.({ tabId, color });
|
|
1019
|
+
await chrome.action?.setBadgeText?.({ tabId, text });
|
|
1020
|
+
await chrome.action?.setTitle?.({ tabId, title });
|
|
1000
1021
|
} catch (error) {
|
|
1001
|
-
log("set cache indicator failed", { tabId, error });
|
|
1022
|
+
log("set cache indicator failed", { tabId, text, error });
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
async function setCacheAvailableIndicator(tabId, expectedUrl) {
|
|
1026
|
+
await setCacheBadgeIndicator(
|
|
1027
|
+
tabId,
|
|
1028
|
+
expectedUrl,
|
|
1029
|
+
CACHE_AVAILABLE_BADGE_TEXT,
|
|
1030
|
+
CACHE_AVAILABLE_BADGE_COLOR,
|
|
1031
|
+
"Tab Control - page cache available"
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
async function setCacheWaitingIndicator(tabId, expectedUrl, detail) {
|
|
1035
|
+
await setCacheBadgeIndicator(
|
|
1036
|
+
tabId,
|
|
1037
|
+
expectedUrl,
|
|
1038
|
+
CACHE_WAITING_BADGE_TEXT,
|
|
1039
|
+
CACHE_WAITING_BADGE_COLOR,
|
|
1040
|
+
`Tab Control - waiting for page cache (${detail})`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
async function setCacheErrorIndicator(tabId, expectedUrl, detail) {
|
|
1044
|
+
await setCacheBadgeIndicator(
|
|
1045
|
+
tabId,
|
|
1046
|
+
expectedUrl,
|
|
1047
|
+
CACHE_ERROR_BADGE_TEXT,
|
|
1048
|
+
CACHE_ERROR_BADGE_COLOR,
|
|
1049
|
+
`Tab Control - page cache error (${detail})`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
function hasPendingActivePageCacheWork(tabId, url) {
|
|
1053
|
+
const key = activePageCacheKeyForTabId(tabId, url);
|
|
1054
|
+
return activePageCache.inFlightKeys.has(key) || activePageCache.pending?.key === key || activePageCache.quiescentPending?.key === key;
|
|
1055
|
+
}
|
|
1056
|
+
function setActivePageCacheState(tabId, url, kind, detail = ACTIVE_PAGE_CACHE_STATE_DETAILS[kind]) {
|
|
1057
|
+
activePageCache.states.set(activePageCacheKeyForTabId(tabId, url), { kind, detail });
|
|
1058
|
+
}
|
|
1059
|
+
function activePageCacheState(tabId, url) {
|
|
1060
|
+
return activePageCache.states.get(activePageCacheKeyForTabId(tabId, url));
|
|
1061
|
+
}
|
|
1062
|
+
function clearActivePageCacheState(tabId, url) {
|
|
1063
|
+
activePageCache.states.delete(activePageCacheKeyForTabId(tabId, url));
|
|
1064
|
+
}
|
|
1065
|
+
function clearActivePageCacheStatesForTab(tabId) {
|
|
1066
|
+
const prefix = `${tabId}:`;
|
|
1067
|
+
for (const key of activePageCache.states.keys()) {
|
|
1068
|
+
if (key.startsWith(prefix)) {
|
|
1069
|
+
activePageCache.states.delete(key);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function clearPendingFirstActivePageCacheCapture(key) {
|
|
1074
|
+
if (activePageCache.pending?.key === key) {
|
|
1075
|
+
activePageCache.pending = null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function failFirstActivePageCacheCapture(pending, tab, url, detail) {
|
|
1079
|
+
clearPendingFirstActivePageCacheCapture(pending.key);
|
|
1080
|
+
setActivePageCacheState(tab.id, url, "error", detail);
|
|
1081
|
+
void setCacheErrorIndicator(tab.id, url, detail);
|
|
1082
|
+
void requestPageCacheStatusForTab(tab.id, `first-capture:${detail.replace(/\s+/g, "-")}`);
|
|
1083
|
+
}
|
|
1084
|
+
function waitingDetailForActivePageCacheState(state2) {
|
|
1085
|
+
if (state2?.kind === "checking" || state2?.kind === "loading" || state2?.kind === "settling" || state2?.kind === "capturing") {
|
|
1086
|
+
return state2.detail;
|
|
1002
1087
|
}
|
|
1088
|
+
return null;
|
|
1003
1089
|
}
|
|
1004
1090
|
async function applyPageCacheStatus(tabId, expectedUrl, available) {
|
|
1005
1091
|
try {
|
|
@@ -1007,10 +1093,31 @@
|
|
|
1007
1093
|
if (activePageCacheUrl(tab) !== expectedUrl) {
|
|
1008
1094
|
return;
|
|
1009
1095
|
}
|
|
1010
|
-
if (!isEligibleActivePageCacheTab(tab)
|
|
1096
|
+
if (!isEligibleActivePageCacheTab(tab)) {
|
|
1011
1097
|
await clearCacheAvailableIndicator(tabId);
|
|
1012
1098
|
return;
|
|
1013
1099
|
}
|
|
1100
|
+
if (!available) {
|
|
1101
|
+
const cacheState = activePageCacheState(tabId, expectedUrl);
|
|
1102
|
+
const waitingDetail = waitingDetailForActivePageCacheState(cacheState);
|
|
1103
|
+
if (waitingDetail) {
|
|
1104
|
+
await setCacheWaitingIndicator(tabId, expectedUrl, waitingDetail);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (cacheState?.kind === "error") {
|
|
1108
|
+
await setCacheErrorIndicator(tabId, expectedUrl, cacheState.detail);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (hasPendingActivePageCacheWork(tabId, expectedUrl)) {
|
|
1112
|
+
setActivePageCacheState(tabId, expectedUrl, "capturing");
|
|
1113
|
+
await setCacheWaitingIndicator(tabId, expectedUrl, ACTIVE_PAGE_CACHE_STATE_DETAILS.capturing);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
clearActivePageCacheState(tabId, expectedUrl);
|
|
1117
|
+
await clearCacheAvailableIndicator(tabId);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
setActivePageCacheState(tabId, expectedUrl, "cached");
|
|
1014
1121
|
await setCacheAvailableIndicator(tabId, expectedUrl);
|
|
1015
1122
|
} catch {
|
|
1016
1123
|
await clearCacheAvailableIndicator(tabId);
|
|
@@ -1044,7 +1151,8 @@
|
|
|
1044
1151
|
const port = state.port;
|
|
1045
1152
|
if (!port) {
|
|
1046
1153
|
connectNative();
|
|
1047
|
-
|
|
1154
|
+
setActivePageCacheState(tab.id, url, "checking", "native host reconnecting");
|
|
1155
|
+
void setCacheWaitingIndicator(tab.id, url, "native host reconnecting");
|
|
1048
1156
|
return;
|
|
1049
1157
|
}
|
|
1050
1158
|
const id = nextActivePageCacheStatusId();
|
|
@@ -1096,7 +1204,7 @@
|
|
|
1096
1204
|
}
|
|
1097
1205
|
function isEligibleActivePageCacheTab(tab) {
|
|
1098
1206
|
const url = activePageCacheUrl(tab);
|
|
1099
|
-
return typeof tab.id === "number" && Boolean(url) && tab.incognito !== true && !browserState.incognitoTabIds.has(tab.id) && (typeof tab.windowId !== "number" || !browserState.incognitoWindowIds.has(tab.windowId)) && tab.discarded !== true &&
|
|
1207
|
+
return typeof tab.id === "number" && Boolean(url) && tab.incognito !== true && !browserState.incognitoTabIds.has(tab.id) && (typeof tab.windowId !== "number" || !browserState.incognitoWindowIds.has(tab.windowId)) && tab.discarded !== true && isScriptableUrl(url);
|
|
1100
1208
|
}
|
|
1101
1209
|
function clearPendingActivePageCacheCapture() {
|
|
1102
1210
|
if (activePageCache.timer) {
|
|
@@ -1135,6 +1243,17 @@
|
|
|
1135
1243
|
}
|
|
1136
1244
|
}, ACTIVE_PAGE_CACHE_QUIESCENT_DELAY_MS);
|
|
1137
1245
|
}
|
|
1246
|
+
function scheduleFirstActivePageCacheRetry(pending, delayMs) {
|
|
1247
|
+
clearPendingActivePageCacheCapture();
|
|
1248
|
+
activePageCache.pending = pending;
|
|
1249
|
+
activePageCache.timer = setTimeout(() => {
|
|
1250
|
+
activePageCache.timer = null;
|
|
1251
|
+
const retry = activePageCache.pending;
|
|
1252
|
+
if (retry) {
|
|
1253
|
+
void captureFirstSettledActivePageCache(retry);
|
|
1254
|
+
}
|
|
1255
|
+
}, delayMs);
|
|
1256
|
+
}
|
|
1138
1257
|
function scheduleActivePageCacheCapture(tab, reason) {
|
|
1139
1258
|
if (!isEligibleActivePageCacheTab(tab)) {
|
|
1140
1259
|
if (typeof tab.id === "number") {
|
|
@@ -1144,23 +1263,27 @@
|
|
|
1144
1263
|
clearPendingQuiescentActivePageCacheCapture();
|
|
1145
1264
|
return;
|
|
1146
1265
|
}
|
|
1147
|
-
requestPageCacheStatus(tab, reason);
|
|
1148
1266
|
const url = activePageCacheUrl(tab);
|
|
1149
1267
|
const key = activePageCacheKey(tab, url);
|
|
1268
|
+
if (tab.status === "loading") {
|
|
1269
|
+
setActivePageCacheState(tab.id, url, "loading");
|
|
1270
|
+
void setCacheWaitingIndicator(tab.id, url, ACTIVE_PAGE_CACHE_STATE_DETAILS.loading);
|
|
1271
|
+
if (activePageCache.pending?.key !== key) {
|
|
1272
|
+
scheduleFirstActivePageCacheRetry({ tabId: tab.id, reason, key, attempts: 0 }, ACTIVE_PAGE_CACHE_LOADING_RETRY_MS);
|
|
1273
|
+
}
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
setActivePageCacheState(tab.id, url, "checking");
|
|
1277
|
+
void setCacheWaitingIndicator(tab.id, url, ACTIVE_PAGE_CACHE_STATE_DETAILS.checking);
|
|
1278
|
+
requestPageCacheStatus(tab, reason);
|
|
1150
1279
|
scheduleQuiescentActivePageCacheCapture(tab, reason, key);
|
|
1151
1280
|
if (activePageCache.inFlightKeys.has(key) || activePageCache.lastCapturedKey === key) {
|
|
1152
1281
|
return;
|
|
1153
1282
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const pending = activePageCache.pending;
|
|
1159
|
-
activePageCache.pending = null;
|
|
1160
|
-
if (pending) {
|
|
1161
|
-
void captureActivePageCache(pending.tab, pending.reason, pending.key);
|
|
1162
|
-
}
|
|
1163
|
-
}, ACTIVE_PAGE_CACHE_DEBOUNCE_MS);
|
|
1283
|
+
if (activePageCache.pending?.key === key) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
scheduleFirstActivePageCacheRetry({ tabId: tab.id, reason, key, attempts: 0 }, 0);
|
|
1164
1287
|
}
|
|
1165
1288
|
async function scheduleActivePageCacheCaptureForTab(tabId, reason) {
|
|
1166
1289
|
try {
|
|
@@ -1195,6 +1318,83 @@
|
|
|
1195
1318
|
return null;
|
|
1196
1319
|
}
|
|
1197
1320
|
}
|
|
1321
|
+
async function getPageCacheOpenTabs() {
|
|
1322
|
+
const tabs = await chrome.tabs.query({});
|
|
1323
|
+
return tabs.filter((tab) => typeof tab.id === "number").filter((tab) => tab.incognito !== true).map((tab) => ({ tabId: tab.id, url: activePageCacheUrl(tab) })).filter((tab) => tab.url.length > 0 && isScriptableUrl(tab.url));
|
|
1324
|
+
}
|
|
1325
|
+
async function captureFirstSettledActivePageCache(pending) {
|
|
1326
|
+
if (activePageCache.inFlightKeys.has(pending.key) || activePageCache.lastCapturedKey === pending.key) {
|
|
1327
|
+
if (activePageCache.pending?.key === pending.key) {
|
|
1328
|
+
activePageCache.pending = null;
|
|
1329
|
+
}
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
const tab = await currentActivePageCacheTab(pending.tabId, pending.key);
|
|
1333
|
+
if (!tab) {
|
|
1334
|
+
if (activePageCache.pending?.key === pending.key) {
|
|
1335
|
+
activePageCache.pending = null;
|
|
1336
|
+
}
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const tabUrl = activePageCacheUrl(tab);
|
|
1340
|
+
if (tab.status === "loading") {
|
|
1341
|
+
setActivePageCacheState(tab.id, tabUrl, "loading");
|
|
1342
|
+
void setCacheWaitingIndicator(tab.id, tabUrl, ACTIVE_PAGE_CACHE_STATE_DETAILS.loading);
|
|
1343
|
+
if (pending.attempts < ACTIVE_PAGE_CACHE_FIRST_LOADING_MAX_ATTEMPTS) {
|
|
1344
|
+
scheduleFirstActivePageCacheRetry(
|
|
1345
|
+
{ ...pending, attempts: pending.attempts + 1 },
|
|
1346
|
+
ACTIVE_PAGE_CACHE_LOADING_RETRY_MS
|
|
1347
|
+
);
|
|
1348
|
+
} else {
|
|
1349
|
+
failFirstActivePageCacheCapture(pending, tab, tabUrl, "loading attempts exhausted");
|
|
1350
|
+
}
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
setActivePageCacheState(tab.id, tabUrl, "settling");
|
|
1354
|
+
void setCacheWaitingIndicator(tab.id, tabUrl, ACTIVE_PAGE_CACHE_STATE_DETAILS.settling);
|
|
1355
|
+
const probe = await content.probePageQuiescence(
|
|
1356
|
+
tab.id,
|
|
1357
|
+
ACTIVE_PAGE_CACHE_FIRST_QUIESCENT_TIMEOUT_MS,
|
|
1358
|
+
ACTIVE_PAGE_CACHE_FIRST_QUIESCENT_SAMPLE_MS
|
|
1359
|
+
);
|
|
1360
|
+
if (activePageCache.pending?.key !== pending.key) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (!probe.quiet) {
|
|
1364
|
+
if (probe.error || probe.documentReadyState === null) {
|
|
1365
|
+
failFirstActivePageCacheCapture(
|
|
1366
|
+
pending,
|
|
1367
|
+
tab,
|
|
1368
|
+
tabUrl,
|
|
1369
|
+
probe.reason || "page settle probe failed"
|
|
1370
|
+
);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const loading = probe.documentReadyState !== "interactive" && probe.documentReadyState !== "complete";
|
|
1374
|
+
setActivePageCacheState(tab.id, tabUrl, loading ? "loading" : "settling");
|
|
1375
|
+
void setCacheWaitingIndicator(
|
|
1376
|
+
tab.id,
|
|
1377
|
+
tabUrl,
|
|
1378
|
+
loading ? ACTIVE_PAGE_CACHE_STATE_DETAILS.loading : ACTIVE_PAGE_CACHE_STATE_DETAILS.settling
|
|
1379
|
+
);
|
|
1380
|
+
if (pending.attempts < ACTIVE_PAGE_CACHE_FIRST_SETTLING_MAX_ATTEMPTS) {
|
|
1381
|
+
scheduleFirstActivePageCacheRetry(
|
|
1382
|
+
{ ...pending, attempts: pending.attempts + 1 },
|
|
1383
|
+
loading ? ACTIVE_PAGE_CACHE_LOADING_RETRY_MS : ACTIVE_PAGE_CACHE_SETTLING_RETRY_MS
|
|
1384
|
+
);
|
|
1385
|
+
} else {
|
|
1386
|
+
failFirstActivePageCacheCapture(
|
|
1387
|
+
pending,
|
|
1388
|
+
tab,
|
|
1389
|
+
tabUrl,
|
|
1390
|
+
loading ? "loading attempts exhausted" : "settling attempts exhausted"
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
clearPendingFirstActivePageCacheCapture(pending.key);
|
|
1396
|
+
await captureActivePageCache(tab, pending.reason, pending.key);
|
|
1397
|
+
}
|
|
1198
1398
|
async function rescheduleQuiescentActivePageCacheCapture(pending) {
|
|
1199
1399
|
if (pending.attempts >= 2 || isQuiescentCaptureCoolingDown(pending.key)) {
|
|
1200
1400
|
return;
|
|
@@ -1221,6 +1421,9 @@
|
|
|
1221
1421
|
if (!tab) {
|
|
1222
1422
|
return;
|
|
1223
1423
|
}
|
|
1424
|
+
const tabUrl = activePageCacheUrl(tab);
|
|
1425
|
+
setActivePageCacheState(tab.id, tabUrl, "settling");
|
|
1426
|
+
void setCacheWaitingIndicator(tab.id, tabUrl, ACTIVE_PAGE_CACHE_STATE_DETAILS.settling);
|
|
1224
1427
|
const probe = await content.probePageQuiescence(
|
|
1225
1428
|
tab.id,
|
|
1226
1429
|
ACTIVE_PAGE_CACHE_QUIESCENT_TIMEOUT_MS,
|
|
@@ -1241,7 +1444,7 @@
|
|
|
1241
1444
|
connectNative();
|
|
1242
1445
|
return false;
|
|
1243
1446
|
}
|
|
1244
|
-
if (!isEligibleActivePageCacheTab(tab) || activePageCache.inFlightKeys.has(key)) {
|
|
1447
|
+
if (!isEligibleActivePageCacheTab(tab) || tab.status === "loading" || activePageCache.inFlightKeys.has(key)) {
|
|
1245
1448
|
return false;
|
|
1246
1449
|
}
|
|
1247
1450
|
activePageCache.inFlightKeys.add(key);
|
|
@@ -1250,8 +1453,42 @@
|
|
|
1250
1453
|
if (!captureTab) {
|
|
1251
1454
|
return false;
|
|
1252
1455
|
}
|
|
1456
|
+
const captureUrl = activePageCacheUrl(captureTab);
|
|
1457
|
+
if (captureTab.status === "loading") {
|
|
1458
|
+
setActivePageCacheState(captureTab.id, captureUrl, "loading");
|
|
1459
|
+
void setCacheWaitingIndicator(captureTab.id, captureUrl, ACTIVE_PAGE_CACHE_STATE_DETAILS.loading);
|
|
1460
|
+
scheduleFirstActivePageCacheRetry({ tabId: captureTab.id, reason, key, attempts: 0 }, ACTIVE_PAGE_CACHE_LOADING_RETRY_MS);
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
setActivePageCacheState(captureTab.id, captureUrl, "capturing");
|
|
1464
|
+
void setCacheWaitingIndicator(captureTab.id, captureUrl, ACTIVE_PAGE_CACHE_STATE_DETAILS.capturing);
|
|
1253
1465
|
const extraction = await content.extractPageHtml(captureTab.id, ACTIVE_PAGE_CACHE_TIMEOUT_MS, ACTIVE_PAGE_CACHE_MAX_HTML_CHARS);
|
|
1254
1466
|
if (extraction.status !== "READ" || typeof extraction.html !== "string" || extraction.html.length === 0) {
|
|
1467
|
+
log("active page cache extraction not readable", {
|
|
1468
|
+
tabId: captureTab.id,
|
|
1469
|
+
reason,
|
|
1470
|
+
key,
|
|
1471
|
+
status: extraction.status,
|
|
1472
|
+
error: extraction.error,
|
|
1473
|
+
sourceHtmlChars: extraction.sourceHtmlChars,
|
|
1474
|
+
sourceTextChars: extraction.sourceTextChars,
|
|
1475
|
+
documentReadyState: extraction.documentReadyState,
|
|
1476
|
+
truncatedHtml: extraction.truncatedHtml
|
|
1477
|
+
});
|
|
1478
|
+
if (extraction.status === "NOT_LOADED") {
|
|
1479
|
+
const url = activePageCacheUrl(captureTab);
|
|
1480
|
+
setActivePageCacheState(captureTab.id, url, "loading");
|
|
1481
|
+
void setCacheWaitingIndicator(captureTab.id, url, ACTIVE_PAGE_CACHE_STATE_DETAILS.loading);
|
|
1482
|
+
scheduleFirstActivePageCacheRetry(
|
|
1483
|
+
{ tabId: captureTab.id, reason, key, attempts: 0 },
|
|
1484
|
+
ACTIVE_PAGE_CACHE_LOADING_RETRY_MS
|
|
1485
|
+
);
|
|
1486
|
+
} else {
|
|
1487
|
+
const detail = typeof extraction.status === "string" ? extraction.status.toLowerCase().replace(/_/g, " ") : "capture failed";
|
|
1488
|
+
const url = activePageCacheUrl(captureTab);
|
|
1489
|
+
setActivePageCacheState(captureTab.id, url, "error", detail);
|
|
1490
|
+
void setCacheErrorIndicator(captureTab.id, url, detail);
|
|
1491
|
+
}
|
|
1255
1492
|
void requestPageCacheStatusForTab(captureTab.id, `${reason}:capture-failed`);
|
|
1256
1493
|
return false;
|
|
1257
1494
|
}
|
|
@@ -1263,6 +1500,7 @@
|
|
|
1263
1500
|
if (!port) {
|
|
1264
1501
|
return false;
|
|
1265
1502
|
}
|
|
1503
|
+
const openTabs = await getPageCacheOpenTabs();
|
|
1266
1504
|
const id = nextActivePageCacheId();
|
|
1267
1505
|
trackPageCacheStatusRequest(id, verifiedTab.id, activePageCacheUrl(verifiedTab));
|
|
1268
1506
|
port.postMessage({
|
|
@@ -1272,6 +1510,7 @@
|
|
|
1272
1510
|
data: {
|
|
1273
1511
|
reason,
|
|
1274
1512
|
capturedAt: Date.now(),
|
|
1513
|
+
openTabs,
|
|
1275
1514
|
tab: {
|
|
1276
1515
|
tabId: verifiedTab.id,
|
|
1277
1516
|
windowId: verifiedTab.windowId,
|
|
@@ -1294,6 +1533,9 @@
|
|
|
1294
1533
|
return true;
|
|
1295
1534
|
} catch (error) {
|
|
1296
1535
|
log("active page cache capture failed", { tabId: tab.id, reason, error });
|
|
1536
|
+
const url = activePageCacheUrl(tab);
|
|
1537
|
+
setActivePageCacheState(tab.id, url, "error", "capture exception");
|
|
1538
|
+
void setCacheErrorIndicator(tab.id, url, "capture exception");
|
|
1297
1539
|
void requestPageCacheStatusForTab(tab.id, `${reason}:capture-error`);
|
|
1298
1540
|
return false;
|
|
1299
1541
|
} finally {
|
|
@@ -1417,6 +1659,7 @@
|
|
|
1417
1659
|
changeInfo
|
|
1418
1660
|
});
|
|
1419
1661
|
if ("url" in changeInfo || "status" in changeInfo || "discarded" in changeInfo) {
|
|
1662
|
+
clearActivePageCacheStatesForTab(tabId);
|
|
1420
1663
|
void clearCacheAvailableIndicator(tabId);
|
|
1421
1664
|
}
|
|
1422
1665
|
if (tab.active && ("url" in changeInfo || "status" in changeInfo || "discarded" in changeInfo)) {
|
|
@@ -1456,6 +1699,7 @@
|
|
|
1456
1699
|
activePageCache.statusRequests.delete(requestId);
|
|
1457
1700
|
}
|
|
1458
1701
|
});
|
|
1702
|
+
clearActivePageCacheStatesForTab(tabId);
|
|
1459
1703
|
void clearCacheAvailableIndicator(tabId);
|
|
1460
1704
|
});
|
|
1461
1705
|
chrome.tabs?.onActivated?.addListener((activeInfo) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tabctl",
|
|
3
|
-
"version": "0.6.0-rc.
|
|
3
|
+
"version": "0.6.0-rc.12",
|
|
4
4
|
"description": "CLI tool to manage and analyze browser tabs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"typescript": "^5.4.5"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"tabctl-win32-x64": "0.6.0-rc.
|
|
56
|
+
"tabctl-win32-x64": "0.6.0-rc.12"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"normalize-url": "^8.1.1"
|