tabctl 0.6.0-alpha.6 → 0.6.0-alpha.8
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/README.md +2 -2
- package/dist/extension/background.js +240 -69
- package/dist/extension/lib/archive.js +31 -6
- package/dist/extension/lib/groups.js +33 -4
- package/dist/extension/lib/inspect.js +30 -28
- package/dist/extension/lib/move.js +65 -4
- package/dist/extension/lib/screenshot.js +2 -11
- package/dist/extension/lib/tabs.js +79 -17
- package/dist/extension/manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -264,7 +264,7 @@ Relevant knobs: `TABCTL_SOCKET`, `TABCTL_TCP_PORT`, `TABCTL_PROFILE`, `TABCTL_DA
|
|
|
264
264
|
- Runtime command runs can auto-sync extension files when host/extension versions drift; rerun `tabctl reload` if the browser does not pick up changes immediately.
|
|
265
265
|
- For local release-like testing while developing, force runtime sync behavior with `TABCTL_AUTO_SYNC_MODE=release-like`.
|
|
266
266
|
- Disable runtime sync entirely with `TABCTL_AUTO_SYNC_MODE=off`.
|
|
267
|
-
- `tabctl ping --json` is the canonical runtime version check (`
|
|
267
|
+
- `tabctl ping --json` is the canonical runtime version check (`versionsInSync`, `hostBaseVersion`, `baseVersion`).
|
|
268
268
|
- Version metadata is intentionally health-only: regular command payloads (`open`, `list`, etc.) do not include version fields.
|
|
269
269
|
- `tabctl ping` returns connect errors (`ENOENT`, `ECONNREFUSED`, timeout): ensure extension is loaded and active, rerun `tabctl setup`, and in WSL verify `TABCTL_TCP_PORT` or `<dataDir>/tcp-port` matches a listening localhost port.
|
|
270
270
|
- `tabctl doctor --fix --json` includes per-profile connectivity diagnostics in `data.profiles[].connectivity`; if ping remains unhealthy after local repairs, follow `manualSteps`.
|
|
@@ -409,5 +409,5 @@ Notes:
|
|
|
409
409
|
- Selector `attr` supports `href-url`/`src-url` to return absolute http(s) URLs.
|
|
410
410
|
- `screenshot --out` writes per-tab folders into the target directory.
|
|
411
411
|
- `tabctl undo` accepts a positional txid, `--txid`, or `--latest`.
|
|
412
|
-
- `tabctl history --json` returns a JSON array
|
|
412
|
+
- `tabctl history --json` returns a top-level JSON array.
|
|
413
413
|
- `--format` is only supported by `report` (use `--json` elsewhere).
|
|
@@ -318,7 +318,6 @@
|
|
|
318
318
|
const tabs2 = selection.tabs;
|
|
319
319
|
const entries = [];
|
|
320
320
|
let totalTiles = 0;
|
|
321
|
-
const startedAt = Date.now();
|
|
322
321
|
for (let index = 0; index < tabs2.length; index += 1) {
|
|
323
322
|
const tab = tabs2[index];
|
|
324
323
|
const tabId = tab.tabId;
|
|
@@ -374,19 +373,11 @@
|
|
|
374
373
|
deps2.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs2.length, tabId });
|
|
375
374
|
}
|
|
376
375
|
}
|
|
377
|
-
|
|
378
|
-
generatedAt: Date.now(),
|
|
376
|
+
const response = {
|
|
379
377
|
totals: { tabs: tabs2.length, tiles: totalTiles },
|
|
380
|
-
meta: {
|
|
381
|
-
durationMs: Date.now() - startedAt,
|
|
382
|
-
mode,
|
|
383
|
-
format,
|
|
384
|
-
quality: format === "jpeg" ? quality : null,
|
|
385
|
-
tileMaxDim: adjustedTileMaxDim,
|
|
386
|
-
maxBytes: adjustedMaxBytes
|
|
387
|
-
},
|
|
388
378
|
entries
|
|
389
379
|
};
|
|
380
|
+
return response;
|
|
390
381
|
}
|
|
391
382
|
}
|
|
392
383
|
});
|
|
@@ -876,7 +867,7 @@
|
|
|
876
867
|
throw new Error("Missing group update fields");
|
|
877
868
|
}
|
|
878
869
|
const updated = await chrome.tabGroups.update(match.group.groupId, update);
|
|
879
|
-
|
|
870
|
+
const fullResult = {
|
|
880
871
|
groupId: updated.id,
|
|
881
872
|
windowId: updated.windowId,
|
|
882
873
|
title: updated.title,
|
|
@@ -894,6 +885,13 @@
|
|
|
894
885
|
},
|
|
895
886
|
txid: params.txid || null
|
|
896
887
|
};
|
|
888
|
+
return {
|
|
889
|
+
groupId: updated.id,
|
|
890
|
+
windowId: updated.windowId,
|
|
891
|
+
summary: { updatedGroups: 1 },
|
|
892
|
+
undo: fullResult.undo,
|
|
893
|
+
txid: fullResult.txid
|
|
894
|
+
};
|
|
897
895
|
}
|
|
898
896
|
async function groupUngroup2(params, deps2) {
|
|
899
897
|
const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
|
|
@@ -937,7 +935,7 @@
|
|
|
937
935
|
if (tabIds.length) {
|
|
938
936
|
await chrome.tabs.ungroup(tabIds);
|
|
939
937
|
}
|
|
940
|
-
|
|
938
|
+
const fullResult = {
|
|
941
939
|
groupId: match.group.groupId,
|
|
942
940
|
groupTitle: match.group.title || null,
|
|
943
941
|
windowId: match.windowId,
|
|
@@ -955,6 +953,13 @@
|
|
|
955
953
|
},
|
|
956
954
|
txid: params.txid || null
|
|
957
955
|
};
|
|
956
|
+
return {
|
|
957
|
+
groupId: match.group.groupId,
|
|
958
|
+
windowId: match.windowId,
|
|
959
|
+
summary: fullResult.summary,
|
|
960
|
+
undo: fullResult.undo,
|
|
961
|
+
txid: fullResult.txid
|
|
962
|
+
};
|
|
958
963
|
}
|
|
959
964
|
async function groupAssign2(params, deps2) {
|
|
960
965
|
const rawTabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
|
|
@@ -1078,7 +1083,7 @@
|
|
|
1078
1083
|
}
|
|
1079
1084
|
created = true;
|
|
1080
1085
|
}
|
|
1081
|
-
|
|
1086
|
+
const fullResult = {
|
|
1082
1087
|
groupId: assignedGroupId,
|
|
1083
1088
|
groupTitle: targetTitle || groupTitle || null,
|
|
1084
1089
|
windowId: targetWindowId,
|
|
@@ -1100,6 +1105,15 @@
|
|
|
1100
1105
|
},
|
|
1101
1106
|
txid: params.txid || null
|
|
1102
1107
|
};
|
|
1108
|
+
return {
|
|
1109
|
+
groupId: assignedGroupId,
|
|
1110
|
+
windowId: targetWindowId,
|
|
1111
|
+
created,
|
|
1112
|
+
summary: fullResult.summary,
|
|
1113
|
+
skipped: fullResult.skipped,
|
|
1114
|
+
undo: fullResult.undo,
|
|
1115
|
+
txid: fullResult.txid
|
|
1116
|
+
};
|
|
1103
1117
|
}
|
|
1104
1118
|
async function groupGather2(params, deps2) {
|
|
1105
1119
|
const snapshot = await deps2.getTabSnapshot();
|
|
@@ -1167,7 +1181,7 @@
|
|
|
1167
1181
|
});
|
|
1168
1182
|
}
|
|
1169
1183
|
}
|
|
1170
|
-
|
|
1184
|
+
const fullResult = {
|
|
1171
1185
|
merged,
|
|
1172
1186
|
summary: {
|
|
1173
1187
|
mergedGroups: merged.reduce((sum, m) => sum + m.mergedGroupCount, 0),
|
|
@@ -1179,6 +1193,12 @@
|
|
|
1179
1193
|
},
|
|
1180
1194
|
txid: params.txid || null
|
|
1181
1195
|
};
|
|
1196
|
+
return {
|
|
1197
|
+
merged,
|
|
1198
|
+
summary: fullResult.summary,
|
|
1199
|
+
undo: fullResult.undo,
|
|
1200
|
+
txid: fullResult.txid
|
|
1201
|
+
};
|
|
1182
1202
|
}
|
|
1183
1203
|
}
|
|
1184
1204
|
});
|
|
@@ -1460,6 +1480,15 @@
|
|
|
1460
1480
|
const index = Number(value);
|
|
1461
1481
|
return Number.isFinite(index) ? index : null;
|
|
1462
1482
|
}
|
|
1483
|
+
function shapeOpenResult(result) {
|
|
1484
|
+
return {
|
|
1485
|
+
windowId: result.windowId,
|
|
1486
|
+
groupId: result.groupId,
|
|
1487
|
+
createdTabIds: result.created.map((tab) => tab.tabId).filter((id) => typeof id === "number"),
|
|
1488
|
+
skipped: result.skipped,
|
|
1489
|
+
summary: result.summary
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1463
1492
|
function matchIncludes(value, needle) {
|
|
1464
1493
|
if (!needle) {
|
|
1465
1494
|
return false;
|
|
@@ -1651,12 +1680,9 @@
|
|
|
1651
1680
|
groupId2 = null;
|
|
1652
1681
|
}
|
|
1653
1682
|
}
|
|
1654
|
-
return {
|
|
1683
|
+
return shapeOpenResult({
|
|
1655
1684
|
windowId: windowId2,
|
|
1656
1685
|
groupId: groupId2,
|
|
1657
|
-
groupTitle: groupTitle || null,
|
|
1658
|
-
afterGroupTitle: null,
|
|
1659
|
-
insertIndex: null,
|
|
1660
1686
|
created: created2,
|
|
1661
1687
|
skipped: skipped2,
|
|
1662
1688
|
summary: {
|
|
@@ -1664,7 +1690,7 @@
|
|
|
1664
1690
|
skippedUrls: skipped2.length,
|
|
1665
1691
|
grouped: Boolean(groupId2)
|
|
1666
1692
|
}
|
|
1667
|
-
};
|
|
1693
|
+
});
|
|
1668
1694
|
}
|
|
1669
1695
|
const snapshot = await deps2.getTabSnapshot();
|
|
1670
1696
|
let openParams = params;
|
|
@@ -1775,6 +1801,7 @@
|
|
|
1775
1801
|
skipped.push({ url, reason: "create_failed" });
|
|
1776
1802
|
}
|
|
1777
1803
|
}
|
|
1804
|
+
const createdTabIds = new Set(created.map((tab) => tab.tabId).filter((id) => typeof id === "number"));
|
|
1778
1805
|
let groupId = null;
|
|
1779
1806
|
if (groupTitle && created.length > 0) {
|
|
1780
1807
|
try {
|
|
@@ -1799,28 +1826,64 @@
|
|
|
1799
1826
|
if (groupTitle && created.length === 0 && existingGroupId != null) {
|
|
1800
1827
|
groupId = existingGroupId;
|
|
1801
1828
|
}
|
|
1829
|
+
const targetGroupId = groupId ?? existingGroupId;
|
|
1830
|
+
if (targetGroupId != null && created.length > 0) {
|
|
1831
|
+
try {
|
|
1832
|
+
if (createdTabIds.size > 0) {
|
|
1833
|
+
const latestTabs = await chrome.tabs.query({ windowId });
|
|
1834
|
+
const missingGroupTabIds = latestTabs.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
|
|
1835
|
+
if (missingGroupTabIds.length > 0) {
|
|
1836
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: missingGroupTabIds });
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
deps2.log("Failed to enforce grouping for newly opened tabs", error);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1802
1843
|
try {
|
|
1803
1844
|
const freshTabs = await chrome.tabs.query({ windowId });
|
|
1804
1845
|
freshTabs.sort((a, b) => a.index - b.index);
|
|
1805
1846
|
const firstUngroupedIndex = freshTabs.findIndex((t) => (t.groupId ?? -1) === -1);
|
|
1806
1847
|
if (firstUngroupedIndex >= 0) {
|
|
1807
|
-
|
|
1808
|
-
if (
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1848
|
+
let tabIdsToMove = [];
|
|
1849
|
+
if (existingGroupId != null) {
|
|
1850
|
+
tabIdsToMove = freshTabs.filter((tab, i) => i > firstUngroupedIndex && typeof tab.id === "number" && createdTabIds.has(tab.id) && (tab.groupId ?? -1) !== -1).map((tab) => tab.id);
|
|
1851
|
+
} else {
|
|
1852
|
+
tabIdsToMove = freshTabs.filter((tab, i) => i > firstUngroupedIndex && (tab.groupId ?? -1) !== -1).map((tab) => tab.id).filter((id) => typeof id === "number");
|
|
1853
|
+
}
|
|
1854
|
+
if (tabIdsToMove.length > 0) {
|
|
1855
|
+
await chrome.tabs.move(tabIdsToMove, { index: firstUngroupedIndex });
|
|
1813
1856
|
}
|
|
1814
1857
|
}
|
|
1815
1858
|
} catch (err) {
|
|
1816
1859
|
deps2.log("Failed to reorder groups before ungrouped tabs", err);
|
|
1817
1860
|
}
|
|
1818
|
-
|
|
1861
|
+
if (targetGroupId != null && createdTabIds.size > 0) {
|
|
1862
|
+
try {
|
|
1863
|
+
const latestTabs = await chrome.tabs.query({ windowId });
|
|
1864
|
+
const lateUngroupedTabIds = latestTabs.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
|
|
1865
|
+
if (lateUngroupedTabIds.length > 0) {
|
|
1866
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: lateUngroupedTabIds });
|
|
1867
|
+
}
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
deps2.log("Failed post-reorder grouping verification", error);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if (targetGroupId != null && createdTabIds.size > 0) {
|
|
1873
|
+
try {
|
|
1874
|
+
await deps2.delay(250);
|
|
1875
|
+
const delayedTabs = await chrome.tabs.query({ windowId });
|
|
1876
|
+
const delayedUngroupedTabIds = delayedTabs.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
|
|
1877
|
+
if (delayedUngroupedTabIds.length > 0) {
|
|
1878
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: delayedUngroupedTabIds });
|
|
1879
|
+
}
|
|
1880
|
+
} catch (error) {
|
|
1881
|
+
deps2.log("Failed delayed grouping verification", error);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return shapeOpenResult({
|
|
1819
1885
|
windowId,
|
|
1820
1886
|
groupId,
|
|
1821
|
-
groupTitle: groupTitle || null,
|
|
1822
|
-
afterGroupTitle: afterGroupTitle || null,
|
|
1823
|
-
insertIndex,
|
|
1824
1887
|
created,
|
|
1825
1888
|
skipped,
|
|
1826
1889
|
summary: {
|
|
@@ -1828,7 +1891,7 @@
|
|
|
1828
1891
|
skippedUrls: skipped.length,
|
|
1829
1892
|
grouped: Boolean(groupId)
|
|
1830
1893
|
}
|
|
1831
|
-
};
|
|
1894
|
+
});
|
|
1832
1895
|
}
|
|
1833
1896
|
}
|
|
1834
1897
|
});
|
|
@@ -1947,7 +2010,7 @@
|
|
|
1947
2010
|
targetIndex2 = 0;
|
|
1948
2011
|
}
|
|
1949
2012
|
}
|
|
1950
|
-
|
|
2013
|
+
const fullResult2 = {
|
|
1951
2014
|
tabId,
|
|
1952
2015
|
from: { windowId: sourceWindow.windowId, index: sourceTab.index },
|
|
1953
2016
|
to: { windowId: targetWindowId2, index: targetIndex2 },
|
|
@@ -1970,6 +2033,14 @@
|
|
|
1970
2033
|
},
|
|
1971
2034
|
txid: params.txid || null
|
|
1972
2035
|
};
|
|
2036
|
+
return {
|
|
2037
|
+
tabId,
|
|
2038
|
+
fromWindowId: sourceWindow.windowId,
|
|
2039
|
+
toWindowId: targetWindowId2,
|
|
2040
|
+
summary: fullResult2.summary,
|
|
2041
|
+
undo: fullResult2.undo,
|
|
2042
|
+
txid: fullResult2.txid
|
|
2043
|
+
};
|
|
1973
2044
|
}
|
|
1974
2045
|
let normalizedParams = params;
|
|
1975
2046
|
if (params.windowId != null) {
|
|
@@ -1987,7 +2058,7 @@
|
|
|
1987
2058
|
targetIndex -= 1;
|
|
1988
2059
|
}
|
|
1989
2060
|
const moved = await chrome.tabs.move(tabId, { windowId: targetWindowId, index: targetIndex });
|
|
1990
|
-
|
|
2061
|
+
const fullResult = {
|
|
1991
2062
|
tabId,
|
|
1992
2063
|
from: { windowId: sourceWindow.windowId, index: sourceTab.index },
|
|
1993
2064
|
to: { windowId: targetWindowId, index: moved.index },
|
|
@@ -2010,6 +2081,14 @@
|
|
|
2010
2081
|
},
|
|
2011
2082
|
txid: params.txid || null
|
|
2012
2083
|
};
|
|
2084
|
+
return {
|
|
2085
|
+
tabId,
|
|
2086
|
+
fromWindowId: sourceWindow.windowId,
|
|
2087
|
+
toWindowId: targetWindowId,
|
|
2088
|
+
summary: fullResult.summary,
|
|
2089
|
+
undo: fullResult.undo,
|
|
2090
|
+
txid: fullResult.txid
|
|
2091
|
+
};
|
|
2013
2092
|
}
|
|
2014
2093
|
async function moveGroup(params, deps2) {
|
|
2015
2094
|
const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
|
|
@@ -2027,6 +2106,28 @@
|
|
|
2027
2106
|
if (!source.tabs.length) {
|
|
2028
2107
|
throw new Error("Group has no tabs to move");
|
|
2029
2108
|
}
|
|
2109
|
+
const ensureMovedTabsAreGrouped = async (movedTabIds, targetWindowId2, targetGroupId) => {
|
|
2110
|
+
if (!targetGroupId || movedTabIds.length === 0) {
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const movedSet = new Set(movedTabIds);
|
|
2114
|
+
const verify = async (step) => {
|
|
2115
|
+
const tabs3 = await chrome.tabs.query({ windowId: targetWindowId2 });
|
|
2116
|
+
const missingGroupTabIds = tabs3.filter((tab) => typeof tab.id === "number" && movedSet.has(tab.id) && tab.groupId !== targetGroupId).map((tab) => tab.id);
|
|
2117
|
+
if (missingGroupTabIds.length > 0) {
|
|
2118
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: missingGroupTabIds });
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
try {
|
|
2122
|
+
await verify("group-verify");
|
|
2123
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
2124
|
+
await verify("group-verify-delayed");
|
|
2125
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
2126
|
+
await verify("group-verify-delayed-late");
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
deps2.log("Failed to enforce moved group integrity", error);
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2030
2131
|
const newWindow = params.newWindow === true;
|
|
2031
2132
|
const hasTarget = Number.isFinite(params.beforeTabId) || Number.isFinite(params.afterTabId) || typeof params.beforeGroupTitle === "string" && params.beforeGroupTitle.trim() || typeof params.afterGroupTitle === "string" && params.afterGroupTitle.trim();
|
|
2032
2133
|
if (newWindow) {
|
|
@@ -2054,6 +2155,7 @@
|
|
|
2054
2155
|
} catch (error) {
|
|
2055
2156
|
deps2.log("Failed to regroup tabs", error);
|
|
2056
2157
|
}
|
|
2158
|
+
await ensureMovedTabsAreGrouped(tabIds2, targetWindowId2, newGroupId2);
|
|
2057
2159
|
const undoTabs2 = source.tabs.map((tab) => ({
|
|
2058
2160
|
tabId: tab.tabId,
|
|
2059
2161
|
windowId: tab.windowId,
|
|
@@ -2063,7 +2165,7 @@
|
|
|
2063
2165
|
groupColor: tab.groupColor,
|
|
2064
2166
|
groupCollapsed: source.group.collapsed ?? null
|
|
2065
2167
|
})).filter((tab) => typeof tab.tabId === "number");
|
|
2066
|
-
|
|
2168
|
+
const fullResult2 = {
|
|
2067
2169
|
groupId: source.group.groupId,
|
|
2068
2170
|
windowId: source.windowId,
|
|
2069
2171
|
movedToWindowId: targetWindowId2,
|
|
@@ -2081,6 +2183,15 @@
|
|
|
2081
2183
|
},
|
|
2082
2184
|
txid: params.txid || null
|
|
2083
2185
|
};
|
|
2186
|
+
return {
|
|
2187
|
+
groupId: source.group.groupId,
|
|
2188
|
+
windowId: source.windowId,
|
|
2189
|
+
movedToWindowId: targetWindowId2,
|
|
2190
|
+
newGroupId: newGroupId2,
|
|
2191
|
+
summary: fullResult2.summary,
|
|
2192
|
+
undo: fullResult2.undo,
|
|
2193
|
+
txid: fullResult2.txid
|
|
2194
|
+
};
|
|
2084
2195
|
}
|
|
2085
2196
|
const target = resolveMoveTarget(snapshot, params, deps2);
|
|
2086
2197
|
if (target.error) {
|
|
@@ -2125,6 +2236,7 @@
|
|
|
2125
2236
|
deps2.log("Failed to regroup tabs", error);
|
|
2126
2237
|
}
|
|
2127
2238
|
}
|
|
2239
|
+
await ensureMovedTabsAreGrouped(tabIds, targetWindowId, targetWindowId === source.windowId ? source.group.groupId : newGroupId);
|
|
2128
2240
|
const undoTabs = source.tabs.map((tab) => ({
|
|
2129
2241
|
tabId: tab.tabId,
|
|
2130
2242
|
windowId: tab.windowId,
|
|
@@ -2134,7 +2246,7 @@
|
|
|
2134
2246
|
groupColor: tab.groupColor,
|
|
2135
2247
|
groupCollapsed: source.group.collapsed ?? null
|
|
2136
2248
|
})).filter((tab) => typeof tab.tabId === "number");
|
|
2137
|
-
|
|
2249
|
+
const fullResult = {
|
|
2138
2250
|
groupId: source.group.groupId,
|
|
2139
2251
|
windowId: source.windowId,
|
|
2140
2252
|
movedToWindowId: targetWindowId,
|
|
@@ -2152,6 +2264,15 @@
|
|
|
2152
2264
|
},
|
|
2153
2265
|
txid: params.txid || null
|
|
2154
2266
|
};
|
|
2267
|
+
return {
|
|
2268
|
+
groupId: source.group.groupId,
|
|
2269
|
+
windowId: source.windowId,
|
|
2270
|
+
movedToWindowId: targetWindowId,
|
|
2271
|
+
newGroupId,
|
|
2272
|
+
summary: fullResult.summary,
|
|
2273
|
+
undo: fullResult.undo,
|
|
2274
|
+
txid: fullResult.txid
|
|
2275
|
+
};
|
|
2155
2276
|
}
|
|
2156
2277
|
}
|
|
2157
2278
|
});
|
|
@@ -2182,7 +2303,6 @@
|
|
|
2182
2303
|
const selectedTabs = selection.tabs;
|
|
2183
2304
|
const scopeTabs = selectedTabs;
|
|
2184
2305
|
const now = Date.now();
|
|
2185
|
-
const startedAt = Date.now();
|
|
2186
2306
|
const normalizedMap = /* @__PURE__ */ new Map();
|
|
2187
2307
|
const duplicates = /* @__PURE__ */ new Map();
|
|
2188
2308
|
for (const tab of scopeTabs) {
|
|
@@ -2235,19 +2355,16 @@
|
|
|
2235
2355
|
severity
|
|
2236
2356
|
};
|
|
2237
2357
|
});
|
|
2238
|
-
|
|
2239
|
-
generatedAt: Date.now(),
|
|
2358
|
+
const response = {
|
|
2240
2359
|
staleDays,
|
|
2241
2360
|
totals: {
|
|
2242
2361
|
tabs: scopeTabs.length,
|
|
2243
2362
|
analyzed: selectedTabs.length,
|
|
2244
2363
|
candidates: candidates.length
|
|
2245
2364
|
},
|
|
2246
|
-
meta: {
|
|
2247
|
-
durationMs: Date.now() - startedAt
|
|
2248
|
-
},
|
|
2249
2365
|
candidates
|
|
2250
2366
|
};
|
|
2367
|
+
return response;
|
|
2251
2368
|
}
|
|
2252
2369
|
async function inspectTabs(params, requestId, deps2) {
|
|
2253
2370
|
const signalList = Array.isArray(params.signals) && params.signals.length > 0 ? params.signals.map(String) : ["page-meta"];
|
|
@@ -2269,7 +2386,6 @@
|
|
|
2269
2386
|
throw selection.error;
|
|
2270
2387
|
}
|
|
2271
2388
|
const tabs3 = selection.tabs;
|
|
2272
|
-
const startedAt = Date.now();
|
|
2273
2389
|
const selectorSpecs = [];
|
|
2274
2390
|
if (Array.isArray(params.selectorSpecs)) {
|
|
2275
2391
|
selectorSpecs.push(...params.selectorSpecs);
|
|
@@ -2287,17 +2403,24 @@
|
|
|
2287
2403
|
}
|
|
2288
2404
|
}
|
|
2289
2405
|
}
|
|
2290
|
-
const normalizedSelectors = selectorSpecs.filter((spec) => spec && typeof spec.selector === "string" && spec.selector.length > 0).map((spec) =>
|
|
2291
|
-
|
|
2406
|
+
const normalizedSelectors = selectorSpecs.filter((spec) => spec && typeof spec.selector === "string" && spec.selector.length > 0).map((spec) => {
|
|
2407
|
+
const selector = spec.selector;
|
|
2408
|
+
return {
|
|
2409
|
+
name: typeof spec.name === "string" ? spec.name : void 0,
|
|
2410
|
+
selector,
|
|
2411
|
+
attr: typeof spec.attr === "string" ? spec.attr : "text",
|
|
2412
|
+
all: Boolean(spec.all),
|
|
2413
|
+
text: typeof spec.text === "string" && spec.text.trim() ? spec.text.trim() : void 0,
|
|
2414
|
+
textMode: typeof spec.textMode === "string" ? spec.textMode.trim().toLowerCase() : void 0
|
|
2415
|
+
};
|
|
2416
|
+
});
|
|
2417
|
+
const selectorWarnings = normalizedSelectors.filter((spec) => spec.selector.includes(":contains(")).map((spec) => ({
|
|
2418
|
+
code: "unsupported_selector_syntax",
|
|
2419
|
+
signalId: "selector",
|
|
2292
2420
|
selector: spec.selector,
|
|
2293
|
-
attr: typeof spec.attr === "string" ? spec.attr : "text",
|
|
2294
|
-
all: Boolean(spec.all),
|
|
2295
|
-
text: typeof spec.text === "string" && spec.text.trim() ? spec.text.trim() : void 0,
|
|
2296
|
-
textMode: typeof spec.textMode === "string" ? spec.textMode.trim().toLowerCase() : void 0
|
|
2297
|
-
}));
|
|
2298
|
-
const selectorWarnings = normalizedSelectors.filter((spec) => typeof spec.selector === "string" && spec.selector.includes(":contains(")).map((spec) => ({
|
|
2299
2421
|
name: spec.name || spec.selector,
|
|
2300
|
-
|
|
2422
|
+
message: "Selector uses unsupported CSS :contains() syntax.",
|
|
2423
|
+
hint: "Use selector text filters (text/textMode) or a different selector."
|
|
2301
2424
|
}));
|
|
2302
2425
|
const signalDefs = [];
|
|
2303
2426
|
for (const signalId of signalList) {
|
|
@@ -2339,19 +2462,22 @@
|
|
|
2339
2462
|
const tabId = task.tab.tabId;
|
|
2340
2463
|
let result = null;
|
|
2341
2464
|
let error = null;
|
|
2342
|
-
const started = Date.now();
|
|
2343
2465
|
try {
|
|
2344
2466
|
await waitForTabReady2(tabId, params, signalTimeoutMs);
|
|
2345
2467
|
result = await task.signal.run(tabId);
|
|
2468
|
+
if (task.signal.id === "selector" && result && typeof result === "object") {
|
|
2469
|
+
const selectorErrors = Object.keys(result.errors || {});
|
|
2470
|
+
if (selectorErrors.length > 0) {
|
|
2471
|
+
error = `selector failures: ${selectorErrors.join(", ")}`;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2346
2474
|
} catch (err) {
|
|
2347
2475
|
const message = err instanceof Error ? err.message : "signal_error";
|
|
2348
2476
|
error = message;
|
|
2349
2477
|
}
|
|
2350
|
-
const durationMs = Date.now() - started;
|
|
2351
2478
|
const entry = entryMap.get(tabId) || { tab: task.tab, signals: {} };
|
|
2352
2479
|
entry.signals[task.signal.id] = {
|
|
2353
2480
|
ok: error === null,
|
|
2354
|
-
durationMs,
|
|
2355
2481
|
data: result,
|
|
2356
2482
|
error
|
|
2357
2483
|
};
|
|
@@ -2377,21 +2503,18 @@
|
|
|
2377
2503
|
title: entry.tab.title,
|
|
2378
2504
|
signals: entry.signals
|
|
2379
2505
|
}));
|
|
2380
|
-
|
|
2381
|
-
generatedAt: Date.now(),
|
|
2506
|
+
const response = {
|
|
2382
2507
|
totals: {
|
|
2383
2508
|
tabs: tabs3.length,
|
|
2384
2509
|
signals: signalDefs.length,
|
|
2385
2510
|
tasks: totalTasks
|
|
2386
2511
|
},
|
|
2387
|
-
meta: {
|
|
2388
|
-
durationMs: Date.now() - startedAt,
|
|
2389
|
-
signalTimeoutMs,
|
|
2390
|
-
selectorCount: normalizedSelectors.length,
|
|
2391
|
-
selectorWarnings: selectorWarnings.length > 0 ? selectorWarnings : void 0
|
|
2392
|
-
},
|
|
2393
2512
|
entries
|
|
2394
2513
|
};
|
|
2514
|
+
if (selectorWarnings.length > 0) {
|
|
2515
|
+
response.warnings = selectorWarnings;
|
|
2516
|
+
}
|
|
2517
|
+
return response;
|
|
2395
2518
|
}
|
|
2396
2519
|
}
|
|
2397
2520
|
});
|
|
@@ -2871,13 +2994,14 @@
|
|
|
2871
2994
|
})).filter((win) => win.tabs.length > 0);
|
|
2872
2995
|
}
|
|
2873
2996
|
if (windowsToProcess.length === 0) {
|
|
2874
|
-
|
|
2997
|
+
const fullResult2 = {
|
|
2875
2998
|
txid: params.txid || null,
|
|
2876
2999
|
summary: { movedTabs: 0, movedGroups: 0, skippedTabs: 0 },
|
|
2877
3000
|
archiveWindowId: null,
|
|
2878
3001
|
skipped: [],
|
|
2879
3002
|
undo: { action: "archive", tabs: [] }
|
|
2880
3003
|
};
|
|
3004
|
+
return fullResult2;
|
|
2881
3005
|
}
|
|
2882
3006
|
const archiveWindowId = await ensureArchiveWindow(deps2);
|
|
2883
3007
|
const undoTabs = [];
|
|
@@ -2980,7 +3104,7 @@
|
|
|
2980
3104
|
}
|
|
2981
3105
|
}
|
|
2982
3106
|
}
|
|
2983
|
-
|
|
3107
|
+
const fullResult = {
|
|
2984
3108
|
txid: params.txid || null,
|
|
2985
3109
|
summary: {
|
|
2986
3110
|
movedTabs,
|
|
@@ -2994,6 +3118,13 @@
|
|
|
2994
3118
|
tabs: undoTabs
|
|
2995
3119
|
}
|
|
2996
3120
|
};
|
|
3121
|
+
return {
|
|
3122
|
+
txid: fullResult.txid,
|
|
3123
|
+
summary: fullResult.summary,
|
|
3124
|
+
archiveWindowId,
|
|
3125
|
+
skipped: fullResult.skipped,
|
|
3126
|
+
undo: fullResult.undo
|
|
3127
|
+
};
|
|
2997
3128
|
}
|
|
2998
3129
|
async function getTabsByIds(tabIds) {
|
|
2999
3130
|
const results = [];
|
|
@@ -3022,12 +3153,13 @@
|
|
|
3022
3153
|
tabIds = selection.tabs.map((tab) => tab.tabId);
|
|
3023
3154
|
}
|
|
3024
3155
|
if (!tabIds.length) {
|
|
3025
|
-
|
|
3156
|
+
const fullResult2 = {
|
|
3026
3157
|
txid: params.txid || null,
|
|
3027
3158
|
summary: { closedTabs: 0, skippedTabs: 0 },
|
|
3028
3159
|
skipped: [],
|
|
3029
3160
|
undo: { action: "close", tabs: [] }
|
|
3030
3161
|
};
|
|
3162
|
+
return fullResult2;
|
|
3031
3163
|
}
|
|
3032
3164
|
const expectedUrls = params.expectedUrls || {};
|
|
3033
3165
|
const tabInfos = await getTabsByIds(tabIds);
|
|
@@ -3067,7 +3199,7 @@
|
|
|
3067
3199
|
if (validTabs.length > 0) {
|
|
3068
3200
|
await chrome.tabs.remove(validTabs.map((tab) => tab.tabId));
|
|
3069
3201
|
}
|
|
3070
|
-
|
|
3202
|
+
const fullResult = {
|
|
3071
3203
|
txid: params.txid || null,
|
|
3072
3204
|
summary: {
|
|
3073
3205
|
closedTabs: validTabs.length,
|
|
@@ -3085,6 +3217,12 @@
|
|
|
3085
3217
|
}))
|
|
3086
3218
|
}
|
|
3087
3219
|
};
|
|
3220
|
+
return {
|
|
3221
|
+
txid: fullResult.txid,
|
|
3222
|
+
summary: fullResult.summary,
|
|
3223
|
+
skipped: fullResult.skipped,
|
|
3224
|
+
undo: fullResult.undo
|
|
3225
|
+
};
|
|
3088
3226
|
}
|
|
3089
3227
|
async function mergeWindow(params, deps2) {
|
|
3090
3228
|
const fromWindowId = Number.isFinite(params.fromWindowId) ? Number(params.fromWindowId) : Number(params.windowId);
|
|
@@ -3119,7 +3257,7 @@
|
|
|
3119
3257
|
selectedTabs = sourceWindow.tabs.filter((tab) => tabIdSet.has(tab.tabId));
|
|
3120
3258
|
}
|
|
3121
3259
|
if (selectedTabs.length === 0) {
|
|
3122
|
-
|
|
3260
|
+
const fullResult2 = {
|
|
3123
3261
|
fromWindowId,
|
|
3124
3262
|
toWindowId,
|
|
3125
3263
|
summary: { movedTabs: 0, movedGroups: 0, skippedTabs: skipped.length, closedSource: false },
|
|
@@ -3133,6 +3271,7 @@
|
|
|
3133
3271
|
tabs: []
|
|
3134
3272
|
}
|
|
3135
3273
|
};
|
|
3274
|
+
return fullResult2;
|
|
3136
3275
|
}
|
|
3137
3276
|
const orderedTabs = [...selectedTabs].sort((a, b) => {
|
|
3138
3277
|
const aIndex = Number(a.index);
|
|
@@ -3231,7 +3370,7 @@
|
|
|
3231
3370
|
deps2.log("Failed to close source window", error);
|
|
3232
3371
|
}
|
|
3233
3372
|
}
|
|
3234
|
-
|
|
3373
|
+
const fullResult = {
|
|
3235
3374
|
fromWindowId,
|
|
3236
3375
|
toWindowId,
|
|
3237
3376
|
summary: { movedTabs, movedGroups, skippedTabs: skipped.length, closedSource },
|
|
@@ -3246,6 +3385,15 @@
|
|
|
3246
3385
|
},
|
|
3247
3386
|
txid: params.txid || null
|
|
3248
3387
|
};
|
|
3388
|
+
return {
|
|
3389
|
+
fromWindowId,
|
|
3390
|
+
toWindowId,
|
|
3391
|
+
summary: fullResult.summary,
|
|
3392
|
+
skipped: fullResult.skipped,
|
|
3393
|
+
groups: fullResult.groups,
|
|
3394
|
+
undo: fullResult.undo,
|
|
3395
|
+
txid: fullResult.txid
|
|
3396
|
+
};
|
|
3249
3397
|
}
|
|
3250
3398
|
}
|
|
3251
3399
|
});
|
|
@@ -3425,7 +3573,7 @@
|
|
|
3425
3573
|
component: "extension"
|
|
3426
3574
|
};
|
|
3427
3575
|
case "list":
|
|
3428
|
-
return await getTabSnapshot();
|
|
3576
|
+
return shapeListSnapshot(await getTabSnapshot());
|
|
3429
3577
|
case "analyze":
|
|
3430
3578
|
return await inspect.analyzeTabs(params, requestId, deps);
|
|
3431
3579
|
case "inspect":
|
|
@@ -3469,6 +3617,29 @@
|
|
|
3469
3617
|
throw new Error(`Unknown action: ${action}`);
|
|
3470
3618
|
}
|
|
3471
3619
|
}
|
|
3620
|
+
function shapeListSnapshot(snapshot) {
|
|
3621
|
+
return {
|
|
3622
|
+
windows: snapshot.windows.map((win) => ({
|
|
3623
|
+
windowId: win.windowId,
|
|
3624
|
+
focused: win.focused,
|
|
3625
|
+
tabs: (win.tabs || []).map((tab) => ({
|
|
3626
|
+
tabId: tab.tabId,
|
|
3627
|
+
windowId: tab.windowId,
|
|
3628
|
+
url: tab.url,
|
|
3629
|
+
title: tab.title,
|
|
3630
|
+
active: tab.active,
|
|
3631
|
+
groupId: tab.groupId,
|
|
3632
|
+
groupTitle: tab.groupTitle
|
|
3633
|
+
})),
|
|
3634
|
+
groups: (win.groups || []).map((group) => ({
|
|
3635
|
+
groupId: group.groupId,
|
|
3636
|
+
title: group.title,
|
|
3637
|
+
color: group.color,
|
|
3638
|
+
collapsed: group.collapsed
|
|
3639
|
+
}))
|
|
3640
|
+
}))
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3472
3643
|
function resolveWindowIdFromParams(snapshot, value) {
|
|
3473
3644
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3474
3645
|
return value;
|
|
@@ -51,13 +51,14 @@ async function archiveTabs(params, deps) {
|
|
|
51
51
|
.filter((win) => win.tabs.length > 0);
|
|
52
52
|
}
|
|
53
53
|
if (windowsToProcess.length === 0) {
|
|
54
|
-
|
|
54
|
+
const fullResult = {
|
|
55
55
|
txid: params.txid || null,
|
|
56
56
|
summary: { movedTabs: 0, movedGroups: 0, skippedTabs: 0 },
|
|
57
57
|
archiveWindowId: null,
|
|
58
58
|
skipped: [],
|
|
59
59
|
undo: { action: "archive", tabs: [] },
|
|
60
60
|
};
|
|
61
|
+
return fullResult;
|
|
61
62
|
}
|
|
62
63
|
const archiveWindowId = await ensureArchiveWindow(deps);
|
|
63
64
|
const undoTabs = [];
|
|
@@ -169,7 +170,7 @@ async function archiveTabs(params, deps) {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
+
const fullResult = {
|
|
173
174
|
txid: params.txid || null,
|
|
174
175
|
summary: {
|
|
175
176
|
movedTabs,
|
|
@@ -183,6 +184,13 @@ async function archiveTabs(params, deps) {
|
|
|
183
184
|
tabs: undoTabs,
|
|
184
185
|
},
|
|
185
186
|
};
|
|
187
|
+
return {
|
|
188
|
+
txid: fullResult.txid,
|
|
189
|
+
summary: fullResult.summary,
|
|
190
|
+
archiveWindowId,
|
|
191
|
+
skipped: fullResult.skipped,
|
|
192
|
+
undo: fullResult.undo,
|
|
193
|
+
};
|
|
186
194
|
}
|
|
187
195
|
async function getTabsByIds(tabIds) {
|
|
188
196
|
const results = [];
|
|
@@ -212,12 +220,13 @@ async function closeTabs(params, deps) {
|
|
|
212
220
|
tabIds = selection.tabs.map((tab) => tab.tabId);
|
|
213
221
|
}
|
|
214
222
|
if (!tabIds.length) {
|
|
215
|
-
|
|
223
|
+
const fullResult = {
|
|
216
224
|
txid: params.txid || null,
|
|
217
225
|
summary: { closedTabs: 0, skippedTabs: 0 },
|
|
218
226
|
skipped: [],
|
|
219
227
|
undo: { action: "close", tabs: [] },
|
|
220
228
|
};
|
|
229
|
+
return fullResult;
|
|
221
230
|
}
|
|
222
231
|
const expectedUrls = params.expectedUrls || {};
|
|
223
232
|
const tabInfos = await getTabsByIds(tabIds);
|
|
@@ -257,7 +266,7 @@ async function closeTabs(params, deps) {
|
|
|
257
266
|
if (validTabs.length > 0) {
|
|
258
267
|
await chrome.tabs.remove(validTabs.map((tab) => tab.tabId));
|
|
259
268
|
}
|
|
260
|
-
|
|
269
|
+
const fullResult = {
|
|
261
270
|
txid: params.txid || null,
|
|
262
271
|
summary: {
|
|
263
272
|
closedTabs: validTabs.length,
|
|
@@ -275,6 +284,12 @@ async function closeTabs(params, deps) {
|
|
|
275
284
|
})),
|
|
276
285
|
},
|
|
277
286
|
};
|
|
287
|
+
return {
|
|
288
|
+
txid: fullResult.txid,
|
|
289
|
+
summary: fullResult.summary,
|
|
290
|
+
skipped: fullResult.skipped,
|
|
291
|
+
undo: fullResult.undo,
|
|
292
|
+
};
|
|
278
293
|
}
|
|
279
294
|
async function mergeWindow(params, deps) {
|
|
280
295
|
const fromWindowId = Number.isFinite(params.fromWindowId)
|
|
@@ -311,7 +326,7 @@ async function mergeWindow(params, deps) {
|
|
|
311
326
|
selectedTabs = sourceWindow.tabs.filter((tab) => tabIdSet.has(tab.tabId));
|
|
312
327
|
}
|
|
313
328
|
if (selectedTabs.length === 0) {
|
|
314
|
-
|
|
329
|
+
const fullResult = {
|
|
315
330
|
fromWindowId,
|
|
316
331
|
toWindowId,
|
|
317
332
|
summary: { movedTabs: 0, movedGroups: 0, skippedTabs: skipped.length, closedSource: false },
|
|
@@ -325,6 +340,7 @@ async function mergeWindow(params, deps) {
|
|
|
325
340
|
tabs: [],
|
|
326
341
|
},
|
|
327
342
|
};
|
|
343
|
+
return fullResult;
|
|
328
344
|
}
|
|
329
345
|
const orderedTabs = [...selectedTabs].sort((a, b) => {
|
|
330
346
|
const aIndex = Number(a.index);
|
|
@@ -426,7 +442,7 @@ async function mergeWindow(params, deps) {
|
|
|
426
442
|
deps.log("Failed to close source window", error);
|
|
427
443
|
}
|
|
428
444
|
}
|
|
429
|
-
|
|
445
|
+
const fullResult = {
|
|
430
446
|
fromWindowId,
|
|
431
447
|
toWindowId,
|
|
432
448
|
summary: { movedTabs, movedGroups, skippedTabs: skipped.length, closedSource },
|
|
@@ -441,4 +457,13 @@ async function mergeWindow(params, deps) {
|
|
|
441
457
|
},
|
|
442
458
|
txid: params.txid || null,
|
|
443
459
|
};
|
|
460
|
+
return {
|
|
461
|
+
fromWindowId,
|
|
462
|
+
toWindowId,
|
|
463
|
+
summary: fullResult.summary,
|
|
464
|
+
skipped: fullResult.skipped,
|
|
465
|
+
groups: fullResult.groups,
|
|
466
|
+
undo: fullResult.undo,
|
|
467
|
+
txid: fullResult.txid,
|
|
468
|
+
};
|
|
444
469
|
}
|
|
@@ -211,7 +211,7 @@ async function groupUpdate(params, deps) {
|
|
|
211
211
|
throw new Error("Missing group update fields");
|
|
212
212
|
}
|
|
213
213
|
const updated = await chrome.tabGroups.update(match.group.groupId, update);
|
|
214
|
-
|
|
214
|
+
const fullResult = {
|
|
215
215
|
groupId: updated.id,
|
|
216
216
|
windowId: updated.windowId,
|
|
217
217
|
title: updated.title,
|
|
@@ -229,6 +229,13 @@ async function groupUpdate(params, deps) {
|
|
|
229
229
|
},
|
|
230
230
|
txid: params.txid || null,
|
|
231
231
|
};
|
|
232
|
+
return {
|
|
233
|
+
groupId: updated.id,
|
|
234
|
+
windowId: updated.windowId,
|
|
235
|
+
summary: { updatedGroups: 1 },
|
|
236
|
+
undo: fullResult.undo,
|
|
237
|
+
txid: fullResult.txid,
|
|
238
|
+
};
|
|
232
239
|
}
|
|
233
240
|
async function groupUngroup(params, deps) {
|
|
234
241
|
const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
|
|
@@ -277,7 +284,7 @@ async function groupUngroup(params, deps) {
|
|
|
277
284
|
if (tabIds.length) {
|
|
278
285
|
await chrome.tabs.ungroup(tabIds);
|
|
279
286
|
}
|
|
280
|
-
|
|
287
|
+
const fullResult = {
|
|
281
288
|
groupId: match.group.groupId,
|
|
282
289
|
groupTitle: match.group.title || null,
|
|
283
290
|
windowId: match.windowId,
|
|
@@ -295,6 +302,13 @@ async function groupUngroup(params, deps) {
|
|
|
295
302
|
},
|
|
296
303
|
txid: params.txid || null,
|
|
297
304
|
};
|
|
305
|
+
return {
|
|
306
|
+
groupId: match.group.groupId,
|
|
307
|
+
windowId: match.windowId,
|
|
308
|
+
summary: fullResult.summary,
|
|
309
|
+
undo: fullResult.undo,
|
|
310
|
+
txid: fullResult.txid,
|
|
311
|
+
};
|
|
298
312
|
}
|
|
299
313
|
async function groupAssign(params, deps) {
|
|
300
314
|
const rawTabIds = Array.isArray(params.tabIds) ? params.tabIds.map(Number) : [];
|
|
@@ -423,7 +437,7 @@ async function groupAssign(params, deps) {
|
|
|
423
437
|
}
|
|
424
438
|
created = true;
|
|
425
439
|
}
|
|
426
|
-
|
|
440
|
+
const fullResult = {
|
|
427
441
|
groupId: assignedGroupId,
|
|
428
442
|
groupTitle: targetTitle || groupTitle || null,
|
|
429
443
|
windowId: targetWindowId,
|
|
@@ -445,6 +459,15 @@ async function groupAssign(params, deps) {
|
|
|
445
459
|
},
|
|
446
460
|
txid: params.txid || null,
|
|
447
461
|
};
|
|
462
|
+
return {
|
|
463
|
+
groupId: assignedGroupId,
|
|
464
|
+
windowId: targetWindowId,
|
|
465
|
+
created,
|
|
466
|
+
summary: fullResult.summary,
|
|
467
|
+
skipped: fullResult.skipped,
|
|
468
|
+
undo: fullResult.undo,
|
|
469
|
+
txid: fullResult.txid,
|
|
470
|
+
};
|
|
448
471
|
}
|
|
449
472
|
async function groupGather(params, deps) {
|
|
450
473
|
const snapshot = await deps.getTabSnapshot();
|
|
@@ -514,7 +537,7 @@ async function groupGather(params, deps) {
|
|
|
514
537
|
});
|
|
515
538
|
}
|
|
516
539
|
}
|
|
517
|
-
|
|
540
|
+
const fullResult = {
|
|
518
541
|
merged,
|
|
519
542
|
summary: {
|
|
520
543
|
mergedGroups: merged.reduce((sum, m) => sum + m.mergedGroupCount, 0),
|
|
@@ -526,4 +549,10 @@ async function groupGather(params, deps) {
|
|
|
526
549
|
},
|
|
527
550
|
txid: params.txid || null,
|
|
528
551
|
};
|
|
552
|
+
return {
|
|
553
|
+
merged,
|
|
554
|
+
summary: fullResult.summary,
|
|
555
|
+
undo: fullResult.undo,
|
|
556
|
+
txid: fullResult.txid,
|
|
557
|
+
};
|
|
529
558
|
}
|
|
@@ -22,7 +22,6 @@ async function analyzeTabs(params, requestId, deps) {
|
|
|
22
22
|
const selectedTabs = selection.tabs;
|
|
23
23
|
const scopeTabs = selectedTabs;
|
|
24
24
|
const now = Date.now();
|
|
25
|
-
const startedAt = Date.now();
|
|
26
25
|
const normalizedMap = new Map();
|
|
27
26
|
const duplicates = new Map();
|
|
28
27
|
for (const tab of scopeTabs) {
|
|
@@ -78,19 +77,16 @@ async function analyzeTabs(params, requestId, deps) {
|
|
|
78
77
|
severity,
|
|
79
78
|
};
|
|
80
79
|
});
|
|
81
|
-
|
|
82
|
-
generatedAt: Date.now(),
|
|
80
|
+
const response = {
|
|
83
81
|
staleDays,
|
|
84
82
|
totals: {
|
|
85
83
|
tabs: scopeTabs.length,
|
|
86
84
|
analyzed: selectedTabs.length,
|
|
87
85
|
candidates: candidates.length,
|
|
88
86
|
},
|
|
89
|
-
meta: {
|
|
90
|
-
durationMs: Date.now() - startedAt,
|
|
91
|
-
},
|
|
92
87
|
candidates,
|
|
93
88
|
};
|
|
89
|
+
return response;
|
|
94
90
|
}
|
|
95
91
|
async function inspectTabs(params, requestId, deps) {
|
|
96
92
|
const signalList = Array.isArray(params.signals) && params.signals.length > 0
|
|
@@ -120,7 +116,6 @@ async function inspectTabs(params, requestId, deps) {
|
|
|
120
116
|
throw selection.error;
|
|
121
117
|
}
|
|
122
118
|
const tabs = selection.tabs;
|
|
123
|
-
const startedAt = Date.now();
|
|
124
119
|
const selectorSpecs = [];
|
|
125
120
|
if (Array.isArray(params.selectorSpecs)) {
|
|
126
121
|
selectorSpecs.push(...params.selectorSpecs);
|
|
@@ -140,19 +135,26 @@ async function inspectTabs(params, requestId, deps) {
|
|
|
140
135
|
}
|
|
141
136
|
const normalizedSelectors = selectorSpecs
|
|
142
137
|
.filter((spec) => spec && typeof spec.selector === "string" && spec.selector.length > 0)
|
|
143
|
-
.map((spec) =>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
.map((spec) => {
|
|
139
|
+
const selector = spec.selector;
|
|
140
|
+
return {
|
|
141
|
+
name: typeof spec.name === "string" ? spec.name : undefined,
|
|
142
|
+
selector,
|
|
143
|
+
attr: typeof spec.attr === "string" ? spec.attr : "text",
|
|
144
|
+
all: Boolean(spec.all),
|
|
145
|
+
text: typeof spec.text === "string" && spec.text.trim() ? spec.text.trim() : undefined,
|
|
146
|
+
textMode: typeof spec.textMode === "string" ? spec.textMode.trim().toLowerCase() : undefined,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
151
149
|
const selectorWarnings = normalizedSelectors
|
|
152
|
-
.filter((spec) =>
|
|
150
|
+
.filter((spec) => spec.selector.includes(":contains("))
|
|
153
151
|
.map((spec) => ({
|
|
152
|
+
code: "unsupported_selector_syntax",
|
|
153
|
+
signalId: "selector",
|
|
154
|
+
selector: spec.selector,
|
|
154
155
|
name: spec.name || spec.selector,
|
|
155
|
-
|
|
156
|
+
message: "Selector uses unsupported CSS :contains() syntax.",
|
|
157
|
+
hint: "Use selector text filters (text/textMode) or a different selector.",
|
|
156
158
|
}));
|
|
157
159
|
const signalDefs = [];
|
|
158
160
|
for (const signalId of signalList) {
|
|
@@ -195,20 +197,23 @@ async function inspectTabs(params, requestId, deps) {
|
|
|
195
197
|
const tabId = task.tab.tabId;
|
|
196
198
|
let result = null;
|
|
197
199
|
let error = null;
|
|
198
|
-
const started = Date.now();
|
|
199
200
|
try {
|
|
200
201
|
await waitForTabReady(tabId, params, signalTimeoutMs);
|
|
201
202
|
result = await task.signal.run(tabId);
|
|
203
|
+
if (task.signal.id === "selector" && result && typeof result === "object") {
|
|
204
|
+
const selectorErrors = Object.keys(result.errors || {});
|
|
205
|
+
if (selectorErrors.length > 0) {
|
|
206
|
+
error = `selector failures: ${selectorErrors.join(", ")}`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
202
209
|
}
|
|
203
210
|
catch (err) {
|
|
204
211
|
const message = err instanceof Error ? err.message : "signal_error";
|
|
205
212
|
error = message;
|
|
206
213
|
}
|
|
207
|
-
const durationMs = Date.now() - started;
|
|
208
214
|
const entry = entryMap.get(tabId) || { tab: task.tab, signals: {} };
|
|
209
215
|
entry.signals[task.signal.id] = {
|
|
210
216
|
ok: error === null,
|
|
211
|
-
durationMs,
|
|
212
217
|
data: result,
|
|
213
218
|
error,
|
|
214
219
|
};
|
|
@@ -234,19 +239,16 @@ async function inspectTabs(params, requestId, deps) {
|
|
|
234
239
|
title: entry.tab.title,
|
|
235
240
|
signals: entry.signals,
|
|
236
241
|
}));
|
|
237
|
-
|
|
238
|
-
generatedAt: Date.now(),
|
|
242
|
+
const response = {
|
|
239
243
|
totals: {
|
|
240
244
|
tabs: tabs.length,
|
|
241
245
|
signals: signalDefs.length,
|
|
242
246
|
tasks: totalTasks,
|
|
243
247
|
},
|
|
244
|
-
meta: {
|
|
245
|
-
durationMs: Date.now() - startedAt,
|
|
246
|
-
signalTimeoutMs,
|
|
247
|
-
selectorCount: normalizedSelectors.length,
|
|
248
|
-
selectorWarnings: selectorWarnings.length > 0 ? selectorWarnings : undefined,
|
|
249
|
-
},
|
|
250
248
|
entries,
|
|
251
249
|
};
|
|
250
|
+
if (selectorWarnings.length > 0) {
|
|
251
|
+
response.warnings = selectorWarnings;
|
|
252
|
+
}
|
|
253
|
+
return response;
|
|
252
254
|
}
|
|
@@ -121,7 +121,7 @@ async function moveTab(params, deps) {
|
|
|
121
121
|
targetIndex = 0;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
|
|
124
|
+
const fullResult = {
|
|
125
125
|
tabId,
|
|
126
126
|
from: { windowId: sourceWindow.windowId, index: sourceTab.index },
|
|
127
127
|
to: { windowId: targetWindowId, index: targetIndex },
|
|
@@ -144,6 +144,14 @@ async function moveTab(params, deps) {
|
|
|
144
144
|
},
|
|
145
145
|
txid: params.txid || null,
|
|
146
146
|
};
|
|
147
|
+
return {
|
|
148
|
+
tabId,
|
|
149
|
+
fromWindowId: sourceWindow.windowId,
|
|
150
|
+
toWindowId: targetWindowId,
|
|
151
|
+
summary: fullResult.summary,
|
|
152
|
+
undo: fullResult.undo,
|
|
153
|
+
txid: fullResult.txid,
|
|
154
|
+
};
|
|
147
155
|
}
|
|
148
156
|
let normalizedParams = params;
|
|
149
157
|
if (params.windowId != null) {
|
|
@@ -161,7 +169,7 @@ async function moveTab(params, deps) {
|
|
|
161
169
|
targetIndex -= 1;
|
|
162
170
|
}
|
|
163
171
|
const moved = await chrome.tabs.move(tabId, { windowId: targetWindowId, index: targetIndex });
|
|
164
|
-
|
|
172
|
+
const fullResult = {
|
|
165
173
|
tabId,
|
|
166
174
|
from: { windowId: sourceWindow.windowId, index: sourceTab.index },
|
|
167
175
|
to: { windowId: targetWindowId, index: moved.index },
|
|
@@ -184,6 +192,14 @@ async function moveTab(params, deps) {
|
|
|
184
192
|
},
|
|
185
193
|
txid: params.txid || null,
|
|
186
194
|
};
|
|
195
|
+
return {
|
|
196
|
+
tabId,
|
|
197
|
+
fromWindowId: sourceWindow.windowId,
|
|
198
|
+
toWindowId: targetWindowId,
|
|
199
|
+
summary: fullResult.summary,
|
|
200
|
+
undo: fullResult.undo,
|
|
201
|
+
txid: fullResult.txid,
|
|
202
|
+
};
|
|
187
203
|
}
|
|
188
204
|
async function moveGroup(params, deps) {
|
|
189
205
|
const groupId = Number.isFinite(params.groupId) ? Number(params.groupId) : null;
|
|
@@ -203,6 +219,31 @@ async function moveGroup(params, deps) {
|
|
|
203
219
|
if (!source.tabs.length) {
|
|
204
220
|
throw new Error("Group has no tabs to move");
|
|
205
221
|
}
|
|
222
|
+
const ensureMovedTabsAreGrouped = async (movedTabIds, targetWindowId, targetGroupId) => {
|
|
223
|
+
if (!targetGroupId || movedTabIds.length === 0) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const movedSet = new Set(movedTabIds);
|
|
227
|
+
const verify = async (step) => {
|
|
228
|
+
const tabs = await chrome.tabs.query({ windowId: targetWindowId });
|
|
229
|
+
const missingGroupTabIds = tabs
|
|
230
|
+
.filter((tab) => typeof tab.id === "number" && movedSet.has(tab.id) && tab.groupId !== targetGroupId)
|
|
231
|
+
.map((tab) => tab.id);
|
|
232
|
+
if (missingGroupTabIds.length > 0) {
|
|
233
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: missingGroupTabIds });
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
try {
|
|
237
|
+
await verify("group-verify");
|
|
238
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
239
|
+
await verify("group-verify-delayed");
|
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
241
|
+
await verify("group-verify-delayed-late");
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
deps.log("Failed to enforce moved group integrity", error);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
206
247
|
const newWindow = params.newWindow === true;
|
|
207
248
|
const hasTarget = Number.isFinite(params.beforeTabId)
|
|
208
249
|
|| Number.isFinite(params.afterTabId)
|
|
@@ -234,6 +275,7 @@ async function moveGroup(params, deps) {
|
|
|
234
275
|
catch (error) {
|
|
235
276
|
deps.log("Failed to regroup tabs", error);
|
|
236
277
|
}
|
|
278
|
+
await ensureMovedTabsAreGrouped(tabIds, targetWindowId, newGroupId);
|
|
237
279
|
const undoTabs = source.tabs
|
|
238
280
|
.map((tab) => ({
|
|
239
281
|
tabId: tab.tabId,
|
|
@@ -245,7 +287,7 @@ async function moveGroup(params, deps) {
|
|
|
245
287
|
groupCollapsed: source.group.collapsed ?? null,
|
|
246
288
|
}))
|
|
247
289
|
.filter((tab) => typeof tab.tabId === "number");
|
|
248
|
-
|
|
290
|
+
const fullResult = {
|
|
249
291
|
groupId: source.group.groupId,
|
|
250
292
|
windowId: source.windowId,
|
|
251
293
|
movedToWindowId: targetWindowId,
|
|
@@ -263,6 +305,15 @@ async function moveGroup(params, deps) {
|
|
|
263
305
|
},
|
|
264
306
|
txid: params.txid || null,
|
|
265
307
|
};
|
|
308
|
+
return {
|
|
309
|
+
groupId: source.group.groupId,
|
|
310
|
+
windowId: source.windowId,
|
|
311
|
+
movedToWindowId: targetWindowId,
|
|
312
|
+
newGroupId,
|
|
313
|
+
summary: fullResult.summary,
|
|
314
|
+
undo: fullResult.undo,
|
|
315
|
+
txid: fullResult.txid,
|
|
316
|
+
};
|
|
266
317
|
}
|
|
267
318
|
const target = resolveMoveTarget(snapshot, params, deps);
|
|
268
319
|
if (target.error) {
|
|
@@ -310,6 +361,7 @@ async function moveGroup(params, deps) {
|
|
|
310
361
|
deps.log("Failed to regroup tabs", error);
|
|
311
362
|
}
|
|
312
363
|
}
|
|
364
|
+
await ensureMovedTabsAreGrouped(tabIds, targetWindowId, targetWindowId === source.windowId ? source.group.groupId : newGroupId);
|
|
313
365
|
const undoTabs = source.tabs
|
|
314
366
|
.map((tab) => ({
|
|
315
367
|
tabId: tab.tabId,
|
|
@@ -321,7 +373,7 @@ async function moveGroup(params, deps) {
|
|
|
321
373
|
groupCollapsed: source.group.collapsed ?? null,
|
|
322
374
|
}))
|
|
323
375
|
.filter((tab) => typeof tab.tabId === "number");
|
|
324
|
-
|
|
376
|
+
const fullResult = {
|
|
325
377
|
groupId: source.group.groupId,
|
|
326
378
|
windowId: source.windowId,
|
|
327
379
|
movedToWindowId: targetWindowId,
|
|
@@ -339,4 +391,13 @@ async function moveGroup(params, deps) {
|
|
|
339
391
|
},
|
|
340
392
|
txid: params.txid || null,
|
|
341
393
|
};
|
|
394
|
+
return {
|
|
395
|
+
groupId: source.group.groupId,
|
|
396
|
+
windowId: source.windowId,
|
|
397
|
+
movedToWindowId: targetWindowId,
|
|
398
|
+
newGroupId,
|
|
399
|
+
summary: fullResult.summary,
|
|
400
|
+
undo: fullResult.undo,
|
|
401
|
+
txid: fullResult.txid,
|
|
402
|
+
};
|
|
342
403
|
}
|
|
@@ -293,7 +293,6 @@ async function screenshotTabs(params, requestId, deps) {
|
|
|
293
293
|
const tabs = selection.tabs;
|
|
294
294
|
const entries = [];
|
|
295
295
|
let totalTiles = 0;
|
|
296
|
-
const startedAt = Date.now();
|
|
297
296
|
for (let index = 0; index < tabs.length; index += 1) {
|
|
298
297
|
const tab = tabs[index];
|
|
299
298
|
const tabId = tab.tabId;
|
|
@@ -351,17 +350,9 @@ async function screenshotTabs(params, requestId, deps) {
|
|
|
351
350
|
deps.sendProgress(requestId, { phase: "screenshot", processed: index + 1, total: tabs.length, tabId });
|
|
352
351
|
}
|
|
353
352
|
}
|
|
354
|
-
|
|
355
|
-
generatedAt: Date.now(),
|
|
353
|
+
const response = {
|
|
356
354
|
totals: { tabs: tabs.length, tiles: totalTiles },
|
|
357
|
-
meta: {
|
|
358
|
-
durationMs: Date.now() - startedAt,
|
|
359
|
-
mode,
|
|
360
|
-
format,
|
|
361
|
-
quality: format === "jpeg" ? quality : null,
|
|
362
|
-
tileMaxDim: adjustedTileMaxDim,
|
|
363
|
-
maxBytes: adjustedMaxBytes,
|
|
364
|
-
},
|
|
365
355
|
entries,
|
|
366
356
|
};
|
|
357
|
+
return response;
|
|
367
358
|
}
|
|
@@ -58,6 +58,17 @@ function normalizeTabIndex(value) {
|
|
|
58
58
|
const index = Number(value);
|
|
59
59
|
return Number.isFinite(index) ? index : null;
|
|
60
60
|
}
|
|
61
|
+
function shapeOpenResult(result) {
|
|
62
|
+
return {
|
|
63
|
+
windowId: result.windowId,
|
|
64
|
+
groupId: result.groupId,
|
|
65
|
+
createdTabIds: result.created
|
|
66
|
+
.map((tab) => tab.tabId)
|
|
67
|
+
.filter((id) => typeof id === "number"),
|
|
68
|
+
skipped: result.skipped,
|
|
69
|
+
summary: result.summary,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
61
72
|
function matchIncludes(value, needle) {
|
|
62
73
|
if (!needle) {
|
|
63
74
|
return false;
|
|
@@ -260,12 +271,9 @@ async function openTabs(params, deps) {
|
|
|
260
271
|
groupId = null;
|
|
261
272
|
}
|
|
262
273
|
}
|
|
263
|
-
return {
|
|
274
|
+
return shapeOpenResult({
|
|
264
275
|
windowId,
|
|
265
276
|
groupId,
|
|
266
|
-
groupTitle: groupTitle || null,
|
|
267
|
-
afterGroupTitle: null,
|
|
268
|
-
insertIndex: null,
|
|
269
277
|
created,
|
|
270
278
|
skipped,
|
|
271
279
|
summary: {
|
|
@@ -273,7 +281,7 @@ async function openTabs(params, deps) {
|
|
|
273
281
|
skippedUrls: skipped.length,
|
|
274
282
|
grouped: Boolean(groupId),
|
|
275
283
|
},
|
|
276
|
-
};
|
|
284
|
+
});
|
|
277
285
|
}
|
|
278
286
|
const snapshot = await deps.getTabSnapshot();
|
|
279
287
|
let openParams = params;
|
|
@@ -393,6 +401,9 @@ async function openTabs(params, deps) {
|
|
|
393
401
|
skipped.push({ url, reason: "create_failed" });
|
|
394
402
|
}
|
|
395
403
|
}
|
|
404
|
+
const createdTabIds = new Set(created
|
|
405
|
+
.map((tab) => tab.tabId)
|
|
406
|
+
.filter((id) => typeof id === "number"));
|
|
396
407
|
let groupId = null;
|
|
397
408
|
if (groupTitle && created.length > 0) {
|
|
398
409
|
try {
|
|
@@ -421,30 +432,81 @@ async function openTabs(params, deps) {
|
|
|
421
432
|
if (groupTitle && created.length === 0 && existingGroupId != null) {
|
|
422
433
|
groupId = existingGroupId;
|
|
423
434
|
}
|
|
424
|
-
|
|
435
|
+
const targetGroupId = groupId ?? existingGroupId;
|
|
436
|
+
if (targetGroupId != null && created.length > 0) {
|
|
437
|
+
try {
|
|
438
|
+
if (createdTabIds.size > 0) {
|
|
439
|
+
const latestTabs = await chrome.tabs.query({ windowId });
|
|
440
|
+
const missingGroupTabIds = latestTabs
|
|
441
|
+
.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId)
|
|
442
|
+
.map((tab) => tab.id);
|
|
443
|
+
if (missingGroupTabIds.length > 0) {
|
|
444
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: missingGroupTabIds });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
deps.log("Failed to enforce grouping for newly opened tabs", error);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
425
452
|
try {
|
|
426
453
|
const freshTabs = await chrome.tabs.query({ windowId });
|
|
427
454
|
freshTabs.sort((a, b) => a.index - b.index);
|
|
428
455
|
const firstUngroupedIndex = freshTabs.findIndex(t => (t.groupId ?? -1) === -1);
|
|
429
456
|
if (firstUngroupedIndex >= 0) {
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
457
|
+
let tabIdsToMove = [];
|
|
458
|
+
if (existingGroupId != null) {
|
|
459
|
+
// In reuse mode, only reorder tabs created by this operation.
|
|
460
|
+
tabIdsToMove = freshTabs
|
|
461
|
+
.filter((tab, i) => i > firstUngroupedIndex && typeof tab.id === "number" && createdTabIds.has(tab.id) && (tab.groupId ?? -1) !== -1)
|
|
462
|
+
.map((tab) => tab.id);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
tabIdsToMove = freshTabs
|
|
466
|
+
.filter((tab, i) => i > firstUngroupedIndex && (tab.groupId ?? -1) !== -1)
|
|
467
|
+
.map((tab) => tab.id)
|
|
468
|
+
.filter((id) => typeof id === "number");
|
|
469
|
+
}
|
|
470
|
+
if (tabIdsToMove.length > 0) {
|
|
471
|
+
await chrome.tabs.move(tabIdsToMove, { index: firstUngroupedIndex });
|
|
436
472
|
}
|
|
437
473
|
}
|
|
438
474
|
}
|
|
439
475
|
catch (err) {
|
|
440
476
|
deps.log("Failed to reorder groups before ungrouped tabs", err);
|
|
441
477
|
}
|
|
442
|
-
|
|
478
|
+
if (targetGroupId != null && createdTabIds.size > 0) {
|
|
479
|
+
try {
|
|
480
|
+
const latestTabs = await chrome.tabs.query({ windowId });
|
|
481
|
+
const lateUngroupedTabIds = latestTabs
|
|
482
|
+
.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId)
|
|
483
|
+
.map((tab) => tab.id);
|
|
484
|
+
if (lateUngroupedTabIds.length > 0) {
|
|
485
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: lateUngroupedTabIds });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
deps.log("Failed post-reorder grouping verification", error);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (targetGroupId != null && createdTabIds.size > 0) {
|
|
493
|
+
try {
|
|
494
|
+
await deps.delay(250);
|
|
495
|
+
const delayedTabs = await chrome.tabs.query({ windowId });
|
|
496
|
+
const delayedUngroupedTabIds = delayedTabs
|
|
497
|
+
.filter((tab) => typeof tab.id === "number" && createdTabIds.has(tab.id) && tab.groupId !== targetGroupId)
|
|
498
|
+
.map((tab) => tab.id);
|
|
499
|
+
if (delayedUngroupedTabIds.length > 0) {
|
|
500
|
+
await chrome.tabs.group({ groupId: targetGroupId, tabIds: delayedUngroupedTabIds });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
deps.log("Failed delayed grouping verification", error);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return shapeOpenResult({
|
|
443
508
|
windowId,
|
|
444
509
|
groupId,
|
|
445
|
-
groupTitle: groupTitle || null,
|
|
446
|
-
afterGroupTitle: afterGroupTitle || null,
|
|
447
|
-
insertIndex,
|
|
448
510
|
created,
|
|
449
511
|
skipped,
|
|
450
512
|
summary: {
|
|
@@ -452,5 +514,5 @@ async function openTabs(params, deps) {
|
|
|
452
514
|
skippedUrls: skipped.length,
|
|
453
515
|
grouped: Boolean(groupId),
|
|
454
516
|
},
|
|
455
|
-
};
|
|
517
|
+
});
|
|
456
518
|
}
|